Skip to content

Latest commit

 

History

History
600 lines (498 loc) · 26.2 KB

File metadata and controls

600 lines (498 loc) · 26.2 KB

ImagePickerKMP Banner

CI Licencia Kotlin

Maven Central Versión NPM Descargas NPM Lanzamiento GitHub Estrellas del repositorio GitHub Último commit GitHub

Compose Multiplatform Android iOS Desktop JavaScript WebAssembly


ImagePickerKMP

Librería Multiplataforma de Selección de Imágenes y Cámara (Android, iOS & Desktop)
Construida con Kotlin Multiplatform + Compose Multiplatform + Kotlin/Native

EnglishGitHubMaven CentralPaquete NPMGuía ReactDiscord


Documentación

Guías completas y referencias para cada aspecto de ImagePickerKMP


Demos Android, iOS, Desktop y Web

Cámara Android Recorte Android Cámara iOS Recorte iOS
Demo Cámara Android Demo Recorte Android Demo Cámara iOS Demo Recorte iOS
Desktop Web
Demo Desktop Demo Web

¡Ahora Disponible para Desarrollo Web!

Integración React/JavaScript

¡ImagePickerKMP ahora está disponible como paquete NPM para desarrollo web!

npm install imagepickerkmp

Guía Completa de Integración React →

Características para web:

  • Componentes React listos para usar
  • Soporte TypeScript con definiciones completas de tipos
  • Acceso a Cámara vía WebRTC (móvil y desktop)
  • Selector de Archivos con soporte drag & drop
  • Procesamiento de Imágenes (Base64, Bytes)
  • Cross-Framework (React, Vue, Angular, Vanilla JS)

Acerca de ImagePickerKMP

  • Multiplataforma: Funciona perfectamente en Android e iOS
  • Integración de Cámara: Acceso directo a la cámara con captura de fotos
  • Selección de Galería: Selecciona imágenes de la galería del dispositivo con soporte de compresión
  • Recorte Avanzado de Imágenes: Funcionalidad de recorte multiplataforma con gestión automática de contexto
  • Metadatos EXIF: Extrae GPS, información de cámara y timestamps (Android/iOS)
  • Soporte de PDF: Selección de documentos PDF junto con imágenes
  • Compresión Automática de Imágenes: Optimiza el tamaño de imagen manteniendo la calidad
  • Niveles de Compresión Configurables: Opciones de compresión BAJA, MEDIA, ALTA
  • Procesamiento Asíncrono: UI no bloqueante con integración de Kotlin Coroutines
  • Soporte de Múltiples Formatos: JPEG, PNG, HEIC, HEIF, WebP, GIF, BMP, PDF
  • Funciones de Extensión: Funciones de extensión integradas para facilitar la visualización y manipulación de imágenes
  • UI Personalizable: Diálogos personalizados y vistas de confirmación
  • Manejo de Permisos: Gestión inteligente de permisos para ambas plataformas
  • Integración Fácil: API simple con Compose Multiplatform
  • Experiencia de Usuario Mejorada: Sistema de diseño mejorado con manejo adecuado de zoom y relación de aspecto
  • Altamente Configurable: Opciones extensas de personalización

Inicio Rápido – Integración del Selector de Imágenes Kotlin Multiplatform

⚠️ Requisitos

Requisito Versión mínima
Kotlin 2.3.20 (cambio incompatible — ver CHANGELOG)
Compose Multiplatform 1.10.3
Ktor 3.4.1
Android minSdk 24
Android compileSdk 36

Nota: Esta librería está compilada con Kotlin 2.3.20. Los proyectos que usen Kotlin < 2.3.x obtendrán un error de incompatibilidad de ABI en tiempo de compilación. Si necesitas soporte para Kotlin 2.1.x, usa una versión anterior de esta librería.

Instalación

Usando ImagePickerKMP en Kotlin Multiplatform / Compose Multiplatform

Paso 1: Añadir la dependencia

En tu commonMain build.gradle.kts:

dependencies {
    implementation("io.github.ismoy:imagepickerkmp:{$lastVersion}")
}

Usando ImagePickerKMP en Android Nativo (Jetpack Compose)

Incluso si no estás usando KMP, puedes usar ImagePickerKMP en proyectos Android puros con Jetpack Compose.

Paso 1: Añadir la dependencia

implementation("io.github.ismoy:imagepickerkmp:{$lastVersion}")

Configuración de Permisos iOS

No olvides configurar los permisos específicos de iOS en tu archivo Info.plist:

<key>NSCameraUsageDescription</key>
<string>Necesitamos acceso a la cámara para capturar una foto.</string>

🔄 API Nueva vs API Heredada (Legacy) — Guía de Migración

Resumen: Usa rememberImagePickerKMP para todo código nuevo. Las APIs ImagePickerLauncher / GalleryPickerLauncher están deprecadas y serán eliminadas en una versión futura.

Comparación lado a lado

API Heredada (v1) ⚠️ Deprecada API Nueva (v2) ✅ Recomendada
Cámara ImagePickerLauncher(config = ...) picker.launchCamera()
Galería GalleryPickerLauncher(...) picker.launchGallery()
Resultado Callbacks (onPhotoCaptured, onDismiss, onError) Reactivo: when (picker.result)
Estado Booleans manuales showCamera/showGallery Automático via ImagePickerKMPState
Overrides por lanzamiento No soportado Todos los params opcionales en cada launch*()
Reset Llamar callback onDismiss picker.reset()
Configuración ImagePickerConfig + GalleryPickerConfig ImagePickerKMPConfig (unificado)

Tabla de migración

Patrón legacy Equivalente en API nueva
var showCamera by remember { ... } (eliminar — no se necesita)
showCamera = true picker.launchCamera()
showGallery = true picker.launchGallery()
onPhotoCaptured = { result -> ... } is ImagePickerResult.Success -> result.photos
onDismiss = { showCamera = false } is ImagePickerResult.Dismissed -> ...
onError = { e -> ... } is ImagePickerResult.Error -> result.exception
ImagePickerConfig(cameraCaptureConfig = ...) ImagePickerKMPConfig(cameraCaptureConfig = ...)
GalleryPickerConfig(includeExif = true) ImagePickerKMPConfig(galleryConfig = GalleryConfig(includeExif = true))
GalleryPickerLauncher(allowMultiple = true, selectionLimit = 5) picker.launchGallery(allowMultiple = true, selectionLimit = 5)

Uso Básico

✅ API Nueva (recomendada desde v1.0.35-alpha1)

val picker = rememberImagePickerKMP(
    config = ImagePickerKMPConfig(
        galleryConfig = GalleryConfig(allowMultiple = true, selectionLimit = 10)
    )
)

// Botones que lanzan el picker directamente — sin booleans
Button(onClick = { picker.launchCamera() }) { Text("Cámara") }
Button(onClick = { picker.launchGallery() }) { Text("Galería") }

// Override por lanzamiento — sobreescribe el config global solo para este tap
Button(onClick = {
    picker.launchGallery(
        allowMultiple = true,
        selectionLimit = 5,
        mimeTypes = listOf(MimeType.IMAGE_JPEG)
    )
}) { Text("Galería (solo JPEG, máx 5)") }

// Resultado reactivo
when (val result = picker.result) {
    is ImagePickerResult.Loading   -> CircularProgressIndicator()
    is ImagePickerResult.Success   -> result.photos.forEach { photo ->
        photo.loadPainter()?.let { Image(it, contentDescription = null) }
    }
    is ImagePickerResult.Error     -> Text("Error: ${result.exception.message}", color = Color.Red)
    is ImagePickerResult.Dismissed -> Text("Sin selección", color = Color.Gray)
    is ImagePickerResult.Idle      -> Text("Listo", color = Color.Gray)
}

⚠️ API Heredada (sigue funcionando, migración recomendada)

El compilador mostrará una advertencia de deprecación para guiar la migración. El código sigue compilando y ejecutándose sin cambios.

Paso 2: Lanzar la Cámara

 var showCamera by remember { mutableStateOf(false) }
 var capturedPhoto by remember { mutableStateOf<PhotoResult?>(null) }
if (showCamera) {
    ImagePickerLauncher(  // ⚠️ Deprecado — migrar a rememberImagePickerKMP
        config = ImagePickerConfig(
            enableCrop = false, // Establecer a true si quieres la opción de Recorte
            onPhotoCaptured = { result ->
                capturedPhoto = result
                println("Tamaño de foto de cámara: ${result.fileSize} bytes")
                showCamera = false
            },
            onError = {
                showCamera = false
            },
            onDismiss = {
                showCamera = false
            },
            directCameraLaunch = false, // Solo iOS: lanzar cámara directamente
            cameraCaptureConfig = CameraCaptureConfig(
                compressionLevel = CompressionLevel.HIGH,
                permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
                    skipConfirmation = true
                )
            )
        )
    )
}

Paso 3: Seleccionar de la Galería

var showGallery by remember { mutableStateOf(false) }
var selectedImages by remember { mutableStateOf<List<GalleryPhotoHandler.PhotoResult>>(emptyList()) }
if (showGallery) {
    GalleryPickerLauncher(
        onPhotosSelected = { photos ->
            selectedImages = photos
            showGallery = false
            
            // ✅ Acceder a datos EXIF si está habilitado
            photos.forEach { photo ->
                photo.exif?.let { exif ->
                    println("Ubicación GPS: ${exif.latitude}, ${exif.longitude}")
                    println("Cámara: ${exif.camera}")
                    println("Fecha/Hora: ${exif.dateTime}")
                }
            }
        },
        onError = { error ->
            showGallery = false
        },
        onDismiss = {
            println("Usuario canceló o cerró el selector")
            showGallery = false // Restablecer estado cuando el usuario no selecciona nada
        },
        enableCrop = false, // Establecer a true si quieres la opción de Recorte 
        allowMultiple = true, // False para selección única
        mimeTypes = listOf(MimeType.IMAGE_PNG), // Opcional: filtrar por tipo
        includeExif = true, // ✅ IMPORTANTE: Habilita extracción de metadatos EXIF
        // Para incluir PDFs: listOf(MimeType.IMAGE_PNG, MimeType.APPLICATION_PDF)
    )
}

Button(onClick = { showGallery = true }) {
    Text("Elegir de la Galería")
}

Para más personalización (vistas de confirmación, filtrado MIME, etc.), consulta la guía de integración para KMP.

Funciones de Extensión para Procesamiento de Imágenes

ImagePickerKMP incluye funciones de extensión integradas que simplifican el manejo y visualización de imágenes. Estas extensiones funcionan perfectamente tanto con ImagePickerLauncher como con GalleryPickerLauncher, proporcionando una API unificada para el procesamiento de imágenes multiplataforma.

Funciones de Extensión Disponibles

La librería proporciona cuatro funciones de extensión principales para la conversión fácil de imágenes:

  • loadBytes() - Devuelve la imagen como ByteArray para operaciones de archivo y almacenamiento
  • loadPainter() - Devuelve la imagen como Painter para mostrar directamente en UI de Compose
  • loadImageBitmap() - Devuelve la imagen como ImageBitmap para operaciones gráficas de Compose
  • loadBase64() - Devuelve la imagen como string Base64 para llamadas API y transmisión de red

Beneficios

  • Integración Simplificada: No necesitas lógica compleja de conversión de imágenes
  • Soporte de Múltiples Formatos: Obtén imágenes en diferentes formatos con llamadas de función únicas
  • Optimizado para Rendimiento: Conversión directa sin pasos de procesamiento intermedios
  • Consistencia Multiplataforma: La misma API funciona idénticamente en Android e iOS
  • Eficiente en Memoria: Uso optimizado de memoria durante la conversión de imágenes

Ejemplos de Uso

// Variables de estado
var showCamera by remember { mutableStateOf(false) }
var showGallery by remember { mutableStateOf(false) }
var capturedImage by remember { mutableStateOf<Painter?>(null) }
var selectedPhotos by remember { mutableStateOf<List<GalleryPhotoHandler.PhotoResult>>(emptyList()) }

// Cámara con funciones de extensión
if (showCamera) {
    ImagePickerLauncher(
        config = ImagePickerConfig(
            onPhotoCaptured = { result ->
                // Usar funciones de extensión para obtener diferentes formatos
                val imageBytes = result.loadBytes()        // Para operaciones de archivo
                val imagePainter = result.loadPainter()    // Para mostrar en UI
                val imageBitmap = result.loadImageBitmap() // Para operaciones gráficas
                val imageBase64 = result.loadBase64()      // Para llamadas API
                
                // Operaciones con el sistema de archivos (kotlinx-io)
                val absolutePath = result.absolutePath     // String - ruta absoluta del archivo
                val path = result.asPath()                 // Objeto Path para operaciones de archivo
                val exists = result.exists()               // Comprobar si el archivo existe
                val rawSource = result.asRawSource()       // RawSource para lectura de bajo nivel
                val source = result.asSource()             // Buffered Source para lectura eficiente

                // Copiar la foto a otra ubicación
                val sink = SystemFileSystem
                    .sink(Path("copy.jpg"))
                result.transferToSink(sink)                // Transferir contenido a RawSink

                // Guardar el painter para mostrar después
                capturedImage = imagePainter
                showCamera = false
            },
            onError = { showCamera = false },
            onDismiss = { showCamera = false },
            directCameraLaunch = true
        )
    )
}

// Galería con funciones de extensión
if (showGallery) {
    GalleryPickerLauncher(
        onPhotosSelected = { photos ->
            selectedPhotos = photos
            showGallery = false
        },
        onError = { showGallery = false },
        onDismiss = { showGallery = false },
        allowMultiple = true
    )
}

// Mostrar imagen capturada
capturedImage?.let { painter ->
    Image(
        painter = painter,
        contentDescription = "Foto capturada",
        modifier = Modifier.size(200.dp)
    )
}

// Mostrar fotos seleccionadas
LazyColumn {
    items(selectedPhotos) { photo ->
        photo.loadPainter()?.let { painter ->
            Image(
                painter = painter,
                contentDescription = "Foto seleccionada",
                modifier = Modifier
                    .fillMaxWidth()
                    .height(200.dp)
            )
        }
    }
}

Compresión de Imágenes

Optimiza automáticamente el tamaño de imagen manteniendo la calidad con niveles de compresión configurables.

Niveles de Compresión

  • BAJA: 95% de calidad, dimensión máxima 2560px - Mejor calidad, archivos más grandes
  • MEDIA: 75% de calidad, dimensión máxima 1920px - Calidad/tamaño equilibrado
  • ALTA: 50% de calidad, dimensión máxima 1280px - Archivos más pequeños, bueno para almacenamiento

Ejemplo de Compresión

ImagePickerLauncher(
    config = ImagePickerConfig(
        cameraCaptureConfig = CameraCaptureConfig(
            compressionLevel = CompressionLevel.MEDIUM,
            skipConfirmation = true
        )
    )
)

Extracción de Metadatos EXIF

Extrae información GPS, detalles de cámara y timestamps de las fotos (Android/iOS).

ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result ->
            result.exif?.let { exif ->
                println("📍 Ubicación: ${exif.latitude}, ${exif.longitude}")
                println("📷 Cámara: ${exif.cameraModel}")
                println("📅 Fecha: ${exif.dateTaken}")
                println("⚙️ ISO: ${exif.iso}, Apertura: f/${exif.aperture}")
            }
        },
        cameraCaptureConfig = CameraCaptureConfig(
            includeExif = true  // Solo Android/iOS
        )
    )
)

🚀 Nuevas Mejoras para Android

Selector Inteligente de Galería vs Explorador de Archivos

ImagePickerKMP ahora detecta automáticamente qué tipo de selector usar en Android:

Tipo de Contenido Comportamiento Resultado
Solo Imágenes (image/*) Abre la galería nativa de Android ✅ Mejor UX para fotos
PDFs (application/pdf) Abre el explorador de archivos ✅ Acceso completo a documentos
Tipos Mixtos Abre el explorador de archivos ✅ Máxima compatibilidad

Ejemplos de Uso

// ✅ Abre GALERÍA automáticamente
GalleryPickerLauncher(
    onPhotosSelected = { photos -> },
    mimeTypes = listOf(MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG)
)

### Extracción de Datos EXIF

Para habilitar datos EXIF (metadatos de ubicación, cámara, etc.):

```kotlin
GalleryPickerLauncher(
    onPhotosSelected = { photos ->
        photos.forEach { photo ->
            photo.exif?.let { exif ->
                println("Ubicación GPS: ${exif.latitude}, ${exif.longitude}")
                println("Cámara: ${exif.camera}")
                println("Fecha: ${exif.dateTime}")
            }
        }
    },
    includeExif = true  // ✅ IMPORTANTE: Habilita extracción EXIF
)

Nota: Por defecto includeExif = false para optimizar rendimiento. Especifica true si necesitas metadatos.


Soporte de Plataformas

Compatibilidad multiplataforma con gestión inteligente de contexto y funcionalidad de recorte mejorada

  • Android: La librería gestiona automáticamente el contexto usando LocalContext.current. No necesitas pasar el contexto manualmente.
  • iOS: El contexto no es requerido ya que la librería usa APIs nativas de iOS. Los cálculos mejorados de coordenadas de recorte aseguran comportamiento consistente con Android.
  • Recorte Multiplataforma: Función applyCrop unificada con gestión automática de contexto y cálculos de coordenadas consistentes entre plataformas.

Mejoras Recientes

  • ** Gestión Automática de Contexto**: La función applyCrop ahora es @Composable y maneja el contexto de Android automáticamente
  • ** Precisión Mejorada de Recorte iOS**: Cálculos de coordenas corregidos para recorte preciso de imágenes en iOS
  • ** Sistema de Diseño Mejorado**: Resueltos conflictos de z-index y problemas de superposición de zoom para mejor experiencia de usuario
  • ** Mejor Soporte de Relación de Aspecto**: Manejo mejorado de relaciones de aspecto verticales (como 9:16) con gestión espacial mejorada
Plataforma Versión Mínima Estado
Android API 21+
iOS iOS 12.0+
Compose Multiplatform 1.5.0+
Desktop (JVM) JDK 11+

¿Por qué Elegir ImagePickerKMP?

El selector de imágenes más completo y amigable para desarrolladores en Kotlin Multiplatform

ImagePickerKMP

Característica ImagePickerKMP
Soporte Compose Multiplatform ✅ Nativo
Personalización de UI ✅ Control completo
Permisos Unificados ✅ Manejo inteligente
Manejo de Errores ✅ Completo
Integración de Cámara ✅ Acceso directo
Soporte de Galería ✅ Selección múltiple
API Multiplataforma ✅ Base de código única

Ventajas Clave

  • ** Nativo de Compose Multiplatform**: Construido específicamente para Compose Multiplatform, asegurando comportamiento consistente entre plataformas
  • ** Personalización Completa de UI**: Control total sobre diálogos, vistas de confirmación, y UI de cámara
  • ** Gestión Inteligente de Permisos**: Manejo unificado de permisos con respaldos inteligentes
  • ** Optimizado para Rendimiento**: Procesamiento eficiente de imágenes y gestión de memoria
  • ** Amigable para Desarrolladores**: API simple con manejo completo de errores

Requisitos

Android

  • SDK Mínimo: 21
  • Kotlin 1.8+
  • Compose Multiplatform

iOS

  • iOS 12.0+
  • Xcode 14+
  • Kotlin Multiplatform

Desktop

  • JVM JDK 11+

Contribuir

¡Damos la bienvenida a las contribuciones de la comunidad!
Consulta nuestra Guía de Contribución para obtener detalles.


Soporte y Comunidad

Obtén ayuda, reporta problemas o únete a nuestra comunidad

📧 Email🐛 Problemas📖 Wiki💬 Discord

Hecho con ❤️ para la comunidad Kotlin Multiplatform
¡Dale una estrella ⭐ a este repo si te ayudó!