This document is also available in English: EXAMPLES.md
// TODO: Traducir el contenido detallado
Consulta la versión en inglés para más ejemplos y casos de uso.
Este documento proporciona ejemplos completos para usar ImagePickerKMP en varios escenarios.
- Ejemplos de Compresión de Imágenes
- Ejemplos de Recorte de Imágenes
- Uso Básico
- Personalización Avanzada
- Manejo de Permisos
- Selección de Galería
- Internacionalización (i18n)
- Manejo de Errores
- Ejemplos Específicos de Plataforma
@Composable
fun CamaraConNivelesCompresion() {
var mostrarCamara by remember { mutableStateOf(false) }
var nivelCompresion by remember { mutableStateOf(CompressionLevel.MEDIUM) }
var fotoCapturada by remember { mutableStateOf<PhotoResult?>(null) }
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("Selecciona Nivel de Compresión:")
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = { nivelCompresion = CompressionLevel.LOW }) {
Text("BAJA (Mejor Calidad)")
}
Button(onClick = { nivelCompresion = CompressionLevel.MEDIUM }) {
Text("MEDIA")
}
Button(onClick = { nivelCompresion = CompressionLevel.HIGH }) {
Text("ALTA (Menor Tamaño)")
}
}
Button(onClick = { mostrarCamara = true }) {
Text("Capturar Foto con Compresión ${nivelCompresion.name}")
}
fotoCapturada?.let { foto ->
val fileSizeKB = (foto.fileSize ?: 0) / 1024.0
Text("Foto capturada - Tamaño: ${String.format("%.2f", fileSizeKB)}KB (${foto.fileSize} bytes)")
Image(
bitmap = foto.photoBytes.toComposeImageBitmap(),
contentDescription = "Foto capturada",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
}
}
if (mostrarCamara) {
CameraCaptureView(
onPhotoTaken = { foto ->
fotoCapturada = foto
mostrarCamara = false
},
onError = { mostrarCamara = false },
onDismiss = { mostrarCamara = false },
cameraCaptureConfig = CameraCaptureConfig(
compressionLevel = nivelCompresion
)
)
}
}@Composable
fun EjemploRecorteSimple() {
var mostrarSelectorImagen by remember { mutableStateOf(false) }
var imagenSeleccionada by remember { mutableStateOf<ByteArray?>(null) }
var mostrarVistaRecorte by remember { mutableStateOf(false) }
var bytesImagenRecortada by remember { mutableStateOf<ByteArray?>(null) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Button(
onClick = { mostrarSelectorImagen = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Seleccionar Imagen para Recortar")
}
imagenSeleccionada?.let { bytesImagen ->
Spacer(modifier = Modifier.height(16.dp))
Text("Imagen Original:")
Image(
bitmap = bytesImagen.toComposeImageBitmap(),
contentDescription = "Imagen original",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
Button(
onClick = { mostrarVistaRecorte = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Recortar Imagen")
}
}
bytesImagenRecortada?.let { bytesRecortados ->
Spacer(modifier = Modifier.height(16.dp))
Text("Imagen Recortada:")
Image(
bitmap = bytesRecortados.toComposeImageBitmap(),
contentDescription = "Imagen recortada",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
}
}
if (mostrarSelectorImagen) {
GalleryPickerLauncher(
onPhotosSelected = { fotos ->
fotos.firstOrNull()?.let { foto ->
imagenSeleccionada = foto.photoBytes
}
mostrarSelectorImagen = false
},
onError = { mostrarSelectorImagen = false },
onDismiss = { mostrarSelectorImagen = false },
allowMultiple = false
)
}
if (mostrarVistaRecorte && imagenSeleccionada != null) {
ImageCropView(
originalImageBytes = imagenSeleccionada!!,
onCropComplete = { bytesRecortados ->
bytesImagenRecortada = bytesRecortados
mostrarVistaRecorte = false
},
onDismiss = { mostrarVistaRecorte = false }
)
}
}@Composable
fun RecorteConRelacionesAspecto() {
var mostrarSelectorImagen by remember { mutableStateOf(false) }
var imagenSeleccionada by remember { mutableStateOf<ByteArray?>(null) }
var mostrarVistaRecorte by remember { mutableStateOf(false) }
var bytesImagenRecortada by remember { mutableStateOf<ByteArray?>(null) }
var relacionAspectoSeleccionada by remember { mutableStateOf<AspectRatio?>(null) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Button(
onClick = { mostrarSelectorImagen = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Seleccionar Imagen para Recortar")
}
imagenSeleccionada?.let { bytesImagen ->
Spacer(modifier = Modifier.height(16.dp))
Text("Selecciona Relación de Aspecto:")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { relacionAspectoSeleccionada = AspectRatio.SQUARE }
) {
Text("1:1")
}
Button(
onClick = { relacionAspectoSeleccionada = AspectRatio.RATIO_4_3 }
) {
Text("4:3")
}
Button(
onClick = { relacionAspectoSeleccionada = AspectRatio.RATIO_16_9 }
) {
Text("16:9")
}
Button(
onClick = { relacionAspectoSeleccionada = AspectRatio.RATIO_9_16 }
) {
Text("9:16")
}
}
relacionAspectoSeleccionada?.let { relacion ->
Spacer(modifier = Modifier.height(8.dp))
Text("Seleccionado: ${relacion.displayName}")
Button(
onClick = { mostrarVistaRecorte = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Recortar con Relación ${relacion.displayName}")
}
}
}
bytesImagenRecortada?.let { bytesRecortados ->
Spacer(modifier = Modifier.height(16.dp))
Text("Imagen Recortada (${relacionAspectoSeleccionada?.displayName}):")
Image(
bitmap = bytesRecortados.toComposeImageBitmap(),
contentDescription = "Imagen recortada",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
}
}
if (mostrarSelectorImagen) {
GalleryPickerLauncher(
onPhotosSelected = { fotos ->
fotos.firstOrNull()?.let { foto ->
imagenSeleccionada = foto.photoBytes
}
mostrarSelectorImagen = false
},
onError = { mostrarSelectorImagen = false },
onDismiss = { mostrarSelectorImagen = false },
allowMultiple = false
)
}
if (mostrarVistaRecorte && imagenSeleccionada != null && relacionAspectoSeleccionada != null) {
ImageCropView(
originalImageBytes = imagenSeleccionada!!,
onCropComplete = { bytesRecortados ->
bytesImagenRecortada = bytesRecortados
mostrarVistaRecorte = false
},
onDismiss = { mostrarVistaRecorte = false },
initialAspectRatio = relacionAspectoSeleccionada
)
}
}@Composable
fun FlujoCamaraRecorte() {
var mostrarCamara by remember { mutableStateOf(false) }
var fotoCapturada by remember { mutableStateOf<PhotoResult?>(null) }
var mostrarVistaRecorte by remember { mutableStateOf(false) }
var bytesImagenRecortada by remember { mutableStateOf<ByteArray?>(null) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Button(
onClick = { mostrarCamara = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Capturar Foto")
}
fotoCapturada?.let { foto ->
Spacer(modifier = Modifier.height(16.dp))
Text("Foto Capturada:")
Image(
bitmap = foto.photoBytes.toComposeImageBitmap(),
contentDescription = "Foto capturada",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
Button(
onClick = { mostrarVistaRecorte = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Recortar Foto")
}
}
bytesImagenRecortada?.let { bytesRecortados ->
Spacer(modifier = Modifier.height(16.dp))
Text("Foto Final Recortada:")
Image(
bitmap = bytesRecortados.toComposeImageBitmap(),
contentDescription = "Foto final recortada",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
}
}
if (mostrarCamara) {
CameraCaptureView(
onPhotoTaken = { foto ->
fotoCapturada = foto
mostrarCamara = false
},
onError = { mostrarCamara = false },
onDismiss = { mostrarCamara = false }
)
}
if (mostrarVistaRecorte && fotoCapturada != null) {
ImageCropView(
originalImageBytes = fotoCapturada!!.photoBytes,
onCropComplete = { bytesRecortados ->
bytesImagenRecortada = bytesRecortados
mostrarVistaRecorte = false
},
onDismiss = { mostrarVistaRecorte = false },
initialAspectRatio = AspectRatio.SQUARE
)
}
}@Composable
fun SelectorImagenSimple() {
var mostrarSelector by remember { mutableStateOf(false) }
if (mostrarSelector) {
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { resultado ->
println("Foto capturada: ${resultado.uri}")
mostrarSelector = false
},
onError = { excepcion ->
println("Error: ${excepcion.message}")
mostrarSelector = false
},
onDismiss = {
println("Usuario canceló o cerró el selector")
mostrarSelector = false // Resetear estado cuando el usuario no selecciona nada
}
)
)
}
Button(onClick = { mostrarSelector = true }) {
Text("Tomar Foto")
}
}@Composable
fun SelectorGaleria() {
var mostrarGaleria by remember { mutableStateOf(false) }
if (mostrarGaleria) {
GalleryPickerLauncher(
onPhotosSelected = { resultados ->
println("Seleccionadas ${resultados.size} imágenes")
mostrarGaleria = false
},
onError = { excepcion ->
println("Error: ${excepcion.message}")
mostrarGaleria = false
},
onDismiss = {
println("Usuario canceló la selección de galería")
mostrarGaleria = false // Resetear estado cuando el usuario no selecciona nada
},
allowMultiple = true
)
}
Button(onClick = { mostrarGaleria = true }) {
Text("Seleccionar de la Galería")
}
}@Composable
fun CustomUIExample() {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception -> /* ... */ },
buttonColor = Color(0xFF6200EE),
iconColor = Color.White,
buttonSize = 56.dp,
flashIcon = Icons.Default.FlashOn,
switchCameraIcon = Icons.Default.CameraRear,
captureIcon = Icons.Default.Camera,
galleryIcon = Icons.Default.PhotoLibrary
)
)
}@Composable
fun CustomCallbacksExample() {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception -> /* ... */ },
onCameraReady = {
println("Camera is ready!")
},
onCameraSwitch = {
println("Camera switched!")
},
onPermissionError = { exception ->
println("Permission error: ${exception.message}")
},
onGalleryOpened = {
println("Gallery opened!")
}
)
)
}@Composable
fun CustomPermissionExample() {
var showPicker by remember { mutableStateOf(false) }
if (showPicker) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> showPicker = false },
onError = { showPicker = false },
cameraCaptureConfig = CameraCaptureConfig(
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customPermissionHandler = { config ->
CustomPermissionDialog(
title = config.titleDialogConfig,
description = config.descriptionDialogConfig,
confirmationButtonText = config.btnDialogConfig,
onConfirm = {
// Manejar solicitud de permiso
}
)
}
)
)
)
)
}
Button(onClick = { showPicker = true }) {
Text("Take Photo")
}
}// Ejemplo con traducciones automáticas
@Composable
fun LocalizedPermissionExample() {
val config = PermissionConfig.createLocalizedComposable()
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception -> /* ... */ },
customPermissionHandler = { _ ->
// Usar la configuración localizada
CustomPermissionDialog(
title = config.titleDialogConfig,
description = config.descriptionDialogConfig,
confirmationButtonText = config.btnDialogConfig,
onConfirm = {
// Manejar solicitud de permiso
}
)
}
)
)
}// Ejemplo de prueba del flujo de permisos
@Composable
fun TestPermissionFlow() {
var showPermissionTest by remember { mutableStateOf(false) }
if (showPermissionTest) {
RequestCameraPermission(
titleDialogConfig = "Camera Permission Required",
descriptionDialogConfig = "Please enable camera access in settings",
btnDialogConfig = "Open Settings",
titleDialogDenied = "Permission Denied",
descriptionDialogDenied = "Camera permission is required. Please try again.",
btnDialogDenied = "Try Again",
onPermissionPermanentlyDenied = {
println("Permission permanently denied - should show settings dialog")
},
onResult = { granted ->
println("Permission result: $granted")
showPermissionTest = false
}
)
}
Button(onClick = { showPermissionTest = true }) {
Text("Test Permission Flow")
}
}@Composable
fun CustomPermissionDialogsExample() {
var showPicker by remember { mutableStateOf(false) }
if (showPicker) {
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { result -> showPicker = false },
onError = { showPicker = false },
onDismiss = { showPicker = false },
cameraCaptureConfig = CameraCaptureConfig(
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
// Diálogo personalizado cuando se deniega el permiso
customDeniedDialog = { onRetry ->
CustomRetryDialog(
title = "Permiso de Cámara Necesario",
message = "Necesitamos acceso a la cámara para tomar fotos",
onRetry = onRetry
)
},
// Diálogo personalizado cuando el permiso es denegado permanentemente
customSettingsDialog = { onOpenSettings ->
CustomSettingsDialog(
title = "Abrir Configuración",
message = "Por favor habilita el permiso de cámara en Configuración",
onOpenSettings = onOpenSettings
)
}
)
)
)
)
}
Button(onClick = { showPicker = true }) {
Text("Tomar Foto con Diálogos de Permisos Personalizados")
}
}
@Composable
fun CustomRetryDialog(
title: String,
message: String,
onRetry: () -> Unit
) {
Dialog(onDismissRequest = { }) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp),
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "📸",
fontSize = 48.sp,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = title,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 12.dp)
)
Text(
text = message,
fontSize = 16.sp,
textAlign = TextAlign.Center,
color = Color.Gray,
modifier = Modifier.padding(bottom = 24.dp)
)
Button(
onClick = onRetry,
modifier = Modifier.fillMaxWidth()
) {
Text("Conceder Permiso")
}
}
}
}
}
@Composable
fun CustomSettingsDialog(
title: String,
message: String,
onOpenSettings: () -> Unit
) {
Dialog(onDismissRequest = { }) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp),
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "⚙️",
fontSize = 48.sp,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = title,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 12.dp)
)
Text(
text = message,
fontSize = 16.sp,
textAlign = TextAlign.Center,
color = Color.Gray,
modifier = Modifier.padding(bottom = 24.dp)
)
Button(
onClick = onOpenSettings,
modifier = Modifier.fillMaxWidth()
) {
Text("Abrir Configuración")
}
}
}
}
}Nota: No necesitas solicitar permisos de galería manualmente. La librería gestiona automáticamente la solicitud de permisos y el flujo de usuario tanto en Android como en iOS, proporcionando una experiencia nativa en cada plataforma.
@Composable
fun GallerySelectionExample() {
var showGallery by remember { mutableStateOf(false) }
if (showGallery) {
GalleryPickerLauncher(
context = LocalContext.current, // Solo Android; ignorado en iOS
onPhotosSelected = { results -> showGallery = false },
onError = { showGallery = false },
allowMultiple = false,
mimeTypes = listOf("image/*") // Opcional
)
}
Button(onClick = { showGallery = true }) {
Text("Seleccionar de la galería")
}
}@Composable
fun MultipleGallerySelectionExample() {
var showGallery by remember { mutableStateOf(false) }
if (showGallery) {
GalleryPickerLauncher(
context = LocalContext.current, // Solo Android; ignorado en iOS
onPhotosSelected = { results -> showGallery = false },
onError = { showGallery = false },
allowMultiple = true,
mimeTypes = listOf("image/jpeg", "image/png") // Opcional
)
}
Button(onClick = { showGallery = true }) {
Text("Seleccionar múltiples imágenes")
}
}@Composable
fun LimitedGallerySelectionExample() {
var showGallery by remember { mutableStateOf(false) }
if (showGallery) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> showGallery = false },
onPhotosSelected = { results -> showGallery = false },
onError = { exception -> showGallery = false },
cameraCaptureConfig = CameraCaptureConfig(
galleryConfig = GalleryConfig(
allowMultiple = true,
mimeTypes = listOf("image/jpeg", "image/png"),
selectionLimit = 10 // Permitir hasta 10 imágenes
)
)
)
)
}
Button(onClick = { showGallery = true }) {
Text("Seleccionar hasta 10 imágenes")
}
}@Composable
fun HighPerformanceGalleryExample() {
var showGallery by remember { mutableStateOf(false) }
if (showGallery) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> showGallery = false },
onPhotosSelected = { results -> showGallery = false },
onError = { exception -> showGallery = false },
cameraCaptureConfig = CameraCaptureConfig(
galleryConfig = GalleryConfig(
allowMultiple = true,
selectionLimit = 5 // Límite conservador para mejor rendimiento
)
)
)
)
}
Button(onClick = { showGallery = true }) {
Text("Seleccionar hasta 5 imágenes (Optimizado)")
}
}- En Android, el usuario verá el selector de galería del sistema y los permisos se solicitan automáticamente si es necesario.
- En iOS, se usa el selector nativo de galería. En iOS 14+ se soporta selección múltiple. El sistema gestiona permisos y acceso limitado de forma nativa.
- El callback
onPhotosSelectedsiempre recibe una lista, incluso para selección simple. - Puedes usar
allowMultiplepara habilitar o deshabilitar la selección múltiple de imágenes. - El parámetro
mimeTypeses opcional y permite filtrar los tipos de archivos seleccionables.
La librería ahora usa automáticamente strings localizados según el idioma del dispositivo. Todo el texto visible para el usuario se traduce automáticamente:
@Composable
fun InternationalizationExample() {
// La librería usa automáticamente strings localizados
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception -> /* ... */ }
// ¡No necesitas especificar texto, se localiza automáticamente!
)
)
}Si necesitas usar strings localizados en tus propios componentes:
@Composable
fun CustomLocalizedComponent() {
Column {
Text(
text = stringResource(StringResource.IMAGE_CONFIRMATION_TITLE)
)
Text(
text = stringResource(StringResource.ACCEPT_BUTTON)
)
Text(
text = stringResource(StringResource.RETRY_BUTTON)
)
}
}Para añadir soporte para un nuevo idioma (por ejemplo, francés):
Crea res/values-fr/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="camera_permission_required">Permission d'appareil photo requise</string>
<string name="image_confirmation_title">Êtes-vous satisfait de la photo ?</string>
<string name="accept_button">Accepter</string>
<string name="retry_button">Réessayer</string>
<!-- Agrega el resto de los strings... -->
</resources>Crea fr.lproj/Localizable.strings:
"camera_permission_required" = "Permission d'appareil photo requise";
"image_confirmation_title" = "Êtes-vous satisfait de la photo ?";
"accept_button" = "Accepter";
"retry_button" = "Réessayer";
/* Agrega el resto de los strings... */// Strings de permisos
StringResource.CAMERA_PERMISSION_REQUIRED
StringResource.CAMERA_PERMISSION_DESCRIPTION
StringResource.OPEN_SETTINGS
StringResource.CAMERA_PERMISSION_DENIED
StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION
StringResource.GRANT_PERMISSION
StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED
// Strings de confirmación
StringResource.IMAGE_CONFIRMATION_TITLE
StringResource.ACCEPT_BUTTON
StringResource.RETRY_BUTTON
// Strings de diálogos
StringResource.SELECT_OPTION_DIALOG_TITLE
StringResource.TAKE_PHOTO_OPTION
StringResource.SELECT_FROM_GALLERY_OPTION
StringResource.CANCEL_OPTION
// Strings de accesibilidad
StringResource.PREVIEW_IMAGE_DESCRIPTION
StringResource.HD_QUALITY_DESCRIPTION
StringResource.SD_QUALITY_DESCRIPTION
// Strings de error
StringResource.INVALID_CONTEXT_ERROR
StringResource.PHOTO_CAPTURE_ERROR
StringResource.GALLERY_SELECTION_ERROR
StringResource.PERMISSION_ERROR@Composable
fun ErrorHandlingExample() {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception ->
when (exception) {
is PhotoCaptureException -> {
println("Photo capture failed: ${exception.message}")
// Mostrar mensaje de error amigable
}
is CameraPermissionException -> {
println("Camera permission denied: ${exception.message}")
// Manejar error de permisos
}
is GallerySelectionException -> {
println("Gallery selection failed: ${exception.message}")
// Manejar error de galería
}
else -> {
println("Unknown error: ${exception.message}")
// Manejar error genérico
}
}
}
)
)
}@Composable
fun CustomErrorMessagesExample() {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> /* ... */ },
onError = { exception ->
val errorMessage = when (exception) {
is PhotoCaptureException -> getStringResource(StringResource.PHOTO_CAPTURE_ERROR)
is CameraPermissionException -> getStringResource(StringResource.PERMISSION_ERROR)
is GallerySelectionException -> getStringResource(StringResource.GALLERY_SELECTION_ERROR)
else -> getStringResource(StringResource.INVALID_CONTEXT_ERROR)
}
// Mostrar mensaje de error localizado
println("Error: $errorMessage")
}
)
)
}// build.gradle.kts (nivel de app)
dependencies {
implementation("io.github.ismoy:imagepickerkmp:1.0.22")
implementation("androidx.compose.ui:ui:1.4.0")
implementation("androidx.compose.material:material:1.4.0")
implementation("androidx.activity:activity-compose:1.7.0")
}
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImagePickerApp()
}
}
}
@Composable
fun ImagePickerApp() {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
ImagePickerScreen()
}
}
}
@Composable
fun ImagePickerScreen() {
var showPicker by remember { mutableStateOf(false) }
var capturedImageUri by remember { mutableStateOf<Uri?>(null) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// Mostrar imagen capturada
capturedImageUri?.let { uri ->
AsyncImage(
model = uri,
contentDescription = "Foto capturada",
modifier = Modifier
.size(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
}
// Botón de cámara
Button(
onClick = { showPicker = true },
modifier = Modifier.padding(8.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.primary
)
) {
Icon(
imageVector = Icons.Default.Camera,
contentDescription = "Cámara",
modifier = Modifier.padding(end = 8.dp)
)
Text("Tomar Foto")
}
if (showPicker) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result ->
capturedImageUri = result.uri
showPicker = false
Toast.makeText(
LocalContext.current,
"¡Foto capturada exitosamente!",
Toast.LENGTH_SHORT
).show()
},
onError = { exception ->
showPicker = false
Toast.makeText(
LocalContext.current,
"Error: ${exception.message}",
Toast.LENGTH_LONG
).show()
}
)
)
}
}
}@Composable
fun AdvancedAndroidImagePicker() {
var showPicker by remember { mutableStateOf(false) }
var imageQuality by remember { mutableStateOf(CapturePhotoPreference.BALANCED) }
Column(
modifier = Modifier.padding(16.dp)
) {
// Selector de calidad
Text("Calidad de Foto:", style = MaterialTheme.typography.h6)
Row(
modifier = Modifier.padding(vertical = 8.dp)
) {
RadioButton(
selected = imageQuality == CapturePhotoPreference.FAST,
onClick = { imageQuality = CapturePhotoPreference.FAST }
)
Text("Rápida", modifier = Modifier.padding(start = 8.dp))
RadioButton(
selected = imageQuality == CapturePhotoPreference.BALANCED,
onClick = { imageQuality = CapturePhotoPreference.BALANCED }
)
Text("Equilibrada", modifier = Modifier.padding(start = 8.dp))
RadioButton(
selected = imageQuality == CapturePhotoPreference.HIGH_QUALITY,
onClick = { imageQuality = CapturePhotoPreference.HIGH_QUALITY }
)
Text("Alta Calidad", modifier = Modifier.padding(start = 8.dp))
}
Button(
onClick = { showPicker = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Tomar Foto con Calidad ${imageQuality.name}")
}
if (showPicker) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result ->
// Procesar la foto capturada
processImage(result.uri)
showPicker = false
},
onError = { exception ->
handleError(exception)
showPicker = false
},
preference = imageQuality,
cameraCaptureConfig = CameraCaptureConfig(
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customConfirmationView = { result, onConfirm, onRetry ->
CustomConfirmationDialog(
result = result,
onConfirm = onConfirm,
onRetry = onRetry,
questionText = "¿Te gusta esta foto?",
retryText = "Otra vez",
acceptText = "Perfecto"
)
}
)
)
)
)
}
}
}
private fun processImage(uri: Uri) {
// Lógica de procesamiento de imagen
println("Procesando imagen: $uri")
}
private fun handleError(exception: Exception) {
println("Error ocurrido: ${exception.message}")
}// Podfile
target 'YourApp' do
use_frameworks!
pod 'ImagePickerKMP', :path => '../path/to/your/library'
end
// ContentView.swift
import SwiftUI
import ImagePickerKMP
struct ContentView: View {
@State private var showImagePicker = false
@State private var capturedImage: UIImage?
@State private var showingAlert = false
@State private var alertMessage = ""
var body: some View {
NavigationView {
VStack(spacing: 20) {
// Mostrar imagen capturada
if let image = capturedImage {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 200)
.cornerRadius(8)
} else {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.3))
.frame(height: 200)
.overlay(
Image(systemName: "camera")
.font(.system(size: 40))
.foregroundColor(.gray)
)
}
// Botón de cámara
Button(action: {
showImagePicker = true
}) {
HStack {
Image(systemName: "camera")
.font(.system(size: 20))
Text("Tomar Foto")
.font(.headline)
}
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
Spacer()
}
.padding()
.navigationTitle("Demo Image Picker")
.sheet(isPresented: $showImagePicker) {
ImagePickerView(
onPhotoCaptured: { result in
// Manejar captura exitosa
print("Foto capturada: \(result.uri)")
showImagePicker = false
// Cargar la imagen
if let url = URL(string: result.uri) {
loadImage(from: url)
}
},
onError: { error in
// Manejar errores
print("Error: \(error.localizedDescription)")
alertMessage = error.localizedDescription
showingAlert = true
showImagePicker = false
}
)
}
.alert("Error", isPresented: $showingAlert) {
Button("OK") { }
} message: {
Text(alertMessage)
}
}
}
private func loadImage(from url: URL) {
// Cargar imagen desde URL
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let image = UIImage(data: data) {
DispatchQueue.main.async {
self.capturedImage = image
}
}
}.resume()
}
}
// ImagePickerView.swift
import SwiftUI
import ImagePickerKMP
struct ImagePickerView: UIViewControllerRepresentable {
let onPhotoCaptured: (PhotoResult) -> Void
let onError: (Error) -> Void
func makeUIViewController(context: Context) -> UIViewController {
let controller = UIViewController()
// Crear ImagePickerKMP launcher
let imagePicker = ImagePickerLauncher(
context: nil, // iOS no necesita context
config = ImagePickerConfig(
onPhotoCaptured: onPhotoCaptured,
onError: onError
)
)
// Presentar el image picker
controller.present(imagePicker, animated: true)
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
// Actualizar si es necesario
}
}// AdvancedContentView.swift
import SwiftUI
import ImagePickerKMP
struct AdvancedContentView: View {
@State private var showImagePicker = false
@State private var capturedImage: UIImage?
@State private var selectedQuality: PhotoQuality = .balanced
enum PhotoQuality: String, CaseIterable {
case fast = "Rápida"
case balanced = "Equilibrada"
case highQuality = "Alta Calidad"
}
var body: some View {
VStack(spacing: 20) {
// Selector de calidad
VStack(alignment: .leading) {
Text("Calidad de Foto:")
.font(.headline)
Picker("Calidad", selection: $selectedQuality) {
ForEach(PhotoQuality.allCases, id: \.self) { quality in
Text(quality.rawValue).tag(quality)
}
}
.pickerStyle(SegmentedPickerStyle())
}
.padding()
// Mostrar imagen capturada
if let image = capturedImage {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 200)
.cornerRadius(8)
}
// Botón de cámara
Button(action: {
showImagePicker = true
}) {
HStack {
Image(systemName: "camera")
.font(.system(size: 20))
Text("Tomar Foto con Calidad \(selectedQuality.rawValue)")
.font(.headline)
}
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
Spacer()
}
.padding()
.navigationTitle("Image Picker Avanzado")
.sheet(isPresented: $showImagePicker) {
AdvancedImagePickerView(
quality: selectedQuality,
onPhotoCaptured: { result in
print("Foto capturada con calidad \(selectedQuality.rawValue): \(result.uri)")
showImagePicker = false
loadImage(from: result.uri)
},
onError: { error in
print("Error: \(error.localizedDescription)")
showImagePicker = false
}
)
}
}
private func loadImage(from uriString: String) {
guard let url = URL(string: uriString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let image = UIImage(data: data) {
DispatchQueue.main.async {
self.capturedImage = image
}
}
}.resume()
}
}
struct AdvancedImagePickerView: UIViewControllerRepresentable {
let quality: PhotoQuality
let onPhotoCaptured: (PhotoResult) -> Void
let onError: (Error) -> Void
func makeUIViewController(context: Context) -> UIViewController {
let controller = UIViewController()
// Crear ImagePickerKMP launcher con configuración personalizada
let imagePicker = ImagePickerLauncher(
context: nil,
config = ImagePickerConfig(
onPhotoCaptured: onPhotoCaptured,
onError: onError,
preference = getPhotoPreference(for: quality)
)
)
controller.present(imagePicker, animated: true)
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
// Actualizar si es necesario
}
private func getPhotoPreference(for quality: PhotoQuality) -> CapturePhotoPreference {
switch quality {
case .fast:
return .FAST
case .balanced:
return .BALANCED
case .highQuality:
return .HIGH_QUALITY
}
}
}// build.gradle.kts (módulo compartido)
kotlin {
android {
// Configuración de Android
}
ios {
binaries {
framework {
baseName = "Shared"
}
}
}
sourceSets {
commonMain {
dependencies {
implementation("io.github.ismoy:imagepickerkmp:1.0.22")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
implementation("org.jetbrains.compose.runtime:runtime:1.4.0")
}
}
androidMain {
dependencies {
implementation("androidx.compose.ui:ui:1.4.0")
implementation("androidx.compose.material:material:1.4.0")
implementation("androidx.activity:activity-compose:1.7.0")
}
}
iosMain {
dependencies {
// Dependencias específicas de iOS si son necesarias
}
}
}
}
// CameraScreen.kt (módulo compartido)
package io.github.ismoy.belzspeedscan.core.camera.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import io.github.ismoy.imagepickerkmp.CameraPhotoHandler
import io.github.ismoy.imagepickerkmp.CapturePhotoPreference
import io.github.ismoy.imagepickerkmp.ImagePickerLauncher
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun CameraScreen(context: Any?) {
var showImagePicker by remember { mutableStateOf(false) }
var capturedImage by remember { mutableStateOf<CameraPhotoHandler.PhotoResult?>(null) }
Scaffold { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.Center
) {
if (showImagePicker) {
ImagePickerLauncher(
context = context,
config = ImagePickerConfig(
onPhotoCaptured = { photoResult ->
capturedImage = photoResult
showImagePicker = false
},
onError = { exception ->
showImagePicker = false
},
preference = CapturePhotoPreference.QUALITY
)
)
} else if (capturedImage != null) {
AsyncImage(
model = capturedImage?.uri,
contentDescription = "Imagen capturada",
modifier = Modifier
.fillMaxSize()
)
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedButton(
onClick = {
showImagePicker = true
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text("Abrir Cámara")
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}// App.kt (aplicación Android)
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
// Pasar el context de la actividad al componente compartido
CameraScreen(context = LocalContext.current)
}
}
}
}
}
// Alternativa: Usar LocalContext directamente en la pantalla
@Composable
fun AndroidCameraScreen() {
val context = LocalContext.current
CameraScreen(context = context)
}// App.kt (aplicación iOS)
import SwiftUI
import ComposeUI
@main
struct ImagePickerApp: App {
var body: some Scene {
WindowGroup {
ComposeView {
// Pasar context null para iOS - la librería lo maneja internamente
CameraScreen(context = null)
}
}
}
}
// Alternativa: Usar wrapper de SwiftUI
struct CameraScreenWrapper: View {
var body: some View {
ComposeView {
CameraScreen(context = null)
}
}
}// App.kt (módulo compartido)
@Composable
fun ImagePickerApp() {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
// El mismo componente funciona en ambas plataformas
// La librería maneja las diferencias de plataforma internamente
CameraScreen(context = null) // El context será proporcionado por la aplicación específica de la plataforma
}
}
}- Código Único: El mismo componente
CameraScreenfunciona en Android e iOS - Abstracción de Plataforma: La librería maneja las diferencias específicas de plataforma internamente
- Manejo de Context:
- Android: Pasar
LocalContext.currento parámetrocontext - iOS: Pasar
null- la librería lo maneja automáticamente
- Android: Pasar
- Sin Detección de Plataforma: No es necesario detectar manualmente la plataforma en tu código
- Arquitectura Limpia: El código específico de plataforma está aislado en la capa de aplicación, no en el componente compartido
Este ejemplo muestra:
- Código base unificado para ambas plataformas
- Manejo automático de plataforma por la librería
- Separación limpia de responsabilidades
- Flujo de desarrollo simplificado
Para más información, consulta Guía de Integración y Referencia de API.
@Composable
fun CustomImagePicker() {
var showPicker by remember { mutableStateOf(false) }
if (showPicker) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> showPicker = false },
onError = { exception -> showPicker = false },
cameraCaptureConfig = CameraCaptureConfig(
preference = CapturePhotoPreference.HIGH_QUALITY,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customPermissionHandler = { config ->
// Manejo personalizado de permisos
},
customConfirmationView = { result, onConfirm, onRetry ->
// Vista de confirmación personalizada
}
)
)
)
)
}
Button(onClick = { showPicker = true }) {
Text("Tomar foto")
}
}