diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06a226f..a25ff96 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ androidx-exifinterface = { module = "androidx.exifinterface:exifinterface", vers androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtendedVersion" } androidx-ui = { module = "androidx.compose.ui:ui", version.ref = "ui" } androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "ui" } +components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose" } core = { module = "com.google.zxing:core", version.ref = "core" } compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose" } compose-animation = { module = "org.jetbrains.compose.animation:animation", version.ref = "compose" } @@ -45,3 +46,4 @@ ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "kto androidLibrary = { id = "com.android.library", version.ref = "agp" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version = "2.0.21" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose" } \ No newline at end of file diff --git a/library/build.gradle.kts b/library/build.gradle.kts index becc78b..2a1097d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -8,6 +8,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) alias(libs.plugins.composeCompiler) + alias(libs.plugins.composeMultiplatform) id("com.vanniktech.maven.publish") version "0.33.0" id("maven-publish") id("jacoco") @@ -382,6 +383,7 @@ kotlin { implementation("io.coil-kt.coil3:coil-compose:3.2.0") implementation("org.jetbrains.compose.material:material-icons-core:1.7.3") implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3") + implementation(libs.components.resources) // OCR dependencies implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") // Ktor dependencies for HTTP client @@ -417,7 +419,6 @@ kotlin { tasks.withType { duplicatesStrategy = DuplicatesStrategy.INCLUDE } - resources.srcDir("src/commonMain/resources") dependencies { // Ktor iOS engine implementation(libs.ktor.client.darwin) diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/features/ocr/presentation/ImagePickerLauncherOCR.android.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/features/ocr/presentation/ImagePickerLauncherOCR.android.kt index 9a25c6c..fd1cfec 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/features/ocr/presentation/ImagePickerLauncherOCR.android.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/features/ocr/presentation/ImagePickerLauncherOCR.android.kt @@ -19,8 +19,6 @@ import io.github.ismoy.imagepickerkmp.domain.config.PermissionAndConfirmationCon import io.github.ismoy.imagepickerkmp.domain.extensions.loadBytes import io.github.ismoy.imagepickerkmp.features.ocr.model.OCRProcessState import io.github.ismoy.imagepickerkmp.features.ocr.model.ScanMode -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.getStringResource import io.github.ismoy.imagepickerkmp.presentation.ui.components.ImagePickerLauncher import io.github.ismoy.imagepickerkmp.features.ocr.presentation.components.OCRProgressDialog import io.github.ismoy.imagepickerkmp.presentation.ui.extensions.activity @@ -32,7 +30,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext - +import org.jetbrains.compose.resources.stringResource +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.invalid_context_error @Suppress("FunctionNaming") @Composable @@ -42,9 +42,10 @@ actual fun ImagePickerLauncherOCR( ) { val context = LocalContext.current val activity = context.activity + val invalidContextErrorMsg = stringResource(Res.string.invalid_context_error) if (activity !is ComponentActivity) { LaunchedEffect(Unit) { - config.onError(Exception(getStringResource(StringResource.INVALID_CONTEXT_ERROR))) + config.onError(Exception(invalidContextErrorMsg)) } return } diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt deleted file mode 100644 index c5d16d6..0000000 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.ismoy.imagepickerkmp.presentation.resources - -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import io.github.ismoy.imagepickerkmp.R - -@Composable -internal actual fun stringResource(id: StringResource): String { - val context = LocalContext.current - return context.getString(getAndroidStringResId(id)) -} - -private val androidStringResMap = mapOf( - StringResource.CAMERA_PERMISSION_REQUIRED to R.string.camera_permission_required, - StringResource.CAMERA_PERMISSION_DESCRIPTION to R.string.camera_permission_description, - StringResource.OPEN_SETTINGS to R.string.open_settings, - StringResource.CAMERA_PERMISSION_DENIED to R.string.camera_permission_denied, - StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION to R.string.camera_permission_denied_description, - StringResource.GRANT_PERMISSION to R.string.grant_permission, - StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED to R.string.camera_permission_permanently_denied, - StringResource.IMAGE_CONFIRMATION_TITLE to R.string.image_confirmation_title, - StringResource.ACCEPT_BUTTON to R.string.accept_button, - StringResource.RETRY_BUTTON to R.string.retry_button, - StringResource.SELECT_OPTION_DIALOG_TITLE to R.string.select_option_dialog_title, - StringResource.TAKE_PHOTO_OPTION to R.string.take_photo_option, - StringResource.SELECT_FROM_GALLERY_OPTION to R.string.select_from_gallery_option, - StringResource.CANCEL_OPTION to R.string.cancel_option, - StringResource.PREVIEW_IMAGE_DESCRIPTION to R.string.preview_image_description, - StringResource.HD_QUALITY_DESCRIPTION to R.string.hd_quality_description, - StringResource.SD_QUALITY_DESCRIPTION to R.string.sd_quality_description, - StringResource.INVALID_CONTEXT_ERROR to R.string.invalid_context_error, - StringResource.PHOTO_CAPTURE_ERROR to R.string.photo_capture_error, - StringResource.GALLERY_SELECTION_ERROR to R.string.gallery_selection_error, - StringResource.PERMISSION_ERROR to R.string.permission_error, - StringResource.GALLERY_PERMISSION_REQUIRED to R.string.gallery_permission_required, - StringResource.GALLERY_PERMISSION_DESCRIPTION to R.string.gallery_permission_description, - StringResource.GALLERY_PERMISSION_DENIED to R.string.gallery_permission_denied, - StringResource.GALLERY_PERMISSION_DENIED_DESCRIPTION to R.string.gallery_permission_denied_description, - StringResource.GALLERY_GRANT_PERMISSION to R.string.gallery_grant_permission, - StringResource.GALLERY_BTN_SETTINGS to R.string.gallery_btn_settings -) -private fun getAndroidStringResId(id: StringResource): Int = - androidStringResMap[id] ?: error("Missing Android string resource mapping for $id") - -@Suppress("CyclomaticComplexMethod") -internal fun getStringResource(id: StringResource): String { - return when (id) { - StringResource.CAMERA_PERMISSION_REQUIRED -> "Camera permission required" - StringResource.CAMERA_PERMISSION_DESCRIPTION -> "Camera permission is required to capture photos." + - " Please grant it in settings" - StringResource.OPEN_SETTINGS -> "Open settings" - StringResource.CAMERA_PERMISSION_DENIED -> "Camera permission denied" - StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION -> "Camera permission is required to" + - " capture photos. Please grant the permissions" - StringResource.GRANT_PERMISSION -> "Grant permission" - StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED -> "Camera permission permanently denied" - StringResource.IMAGE_CONFIRMATION_TITLE -> "Are you satisfied with the photo?" - StringResource.ACCEPT_BUTTON -> "Accept" - StringResource.RETRY_BUTTON -> "Retry" - StringResource.SELECT_OPTION_DIALOG_TITLE -> "Select option" - StringResource.TAKE_PHOTO_OPTION -> "Take photo" - StringResource.SELECT_FROM_GALLERY_OPTION -> "Select from gallery" - StringResource.CANCEL_OPTION -> "Cancel" - StringResource.PREVIEW_IMAGE_DESCRIPTION -> "Preview" - StringResource.HD_QUALITY_DESCRIPTION -> "HD" - StringResource.SD_QUALITY_DESCRIPTION -> "SD" - StringResource.INVALID_CONTEXT_ERROR -> "Invalid context. Must be ComponentActivity" - StringResource.PHOTO_CAPTURE_ERROR -> "Photo capture failed" - StringResource.GALLERY_SELECTION_ERROR -> "Gallery selection failed" - StringResource.PERMISSION_ERROR -> "Permission error occurred" - StringResource.GALLERY_PERMISSION_REQUIRED -> "Storage permission required" - StringResource.GALLERY_PERMISSION_DESCRIPTION -> "Access to storage is required to select " + - "images from your gallery. Please grant the permission." - StringResource.GALLERY_PERMISSION_DENIED -> "Storage permission denied" - StringResource.GALLERY_PERMISSION_DENIED_DESCRIPTION -> "Storage permission is required " + - "to select images. Please enable it in app settings." - StringResource.GALLERY_GRANT_PERMISSION -> "Grant permission" - StringResource.GALLERY_BTN_SETTINGS -> "Open settings" - - } -} diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/GalleryPickerLauncher.android.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/GalleryPickerLauncher.android.kt index 386f6e9..4e530d2 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/GalleryPickerLauncher.android.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/GalleryPickerLauncher.android.kt @@ -7,9 +7,10 @@ import io.github.ismoy.imagepickerkmp.domain.config.CameraCaptureConfig import io.github.ismoy.imagepickerkmp.domain.models.GalleryPhotoResult import io.github.ismoy.imagepickerkmp.domain.models.GalleryPickerConfig import io.github.ismoy.imagepickerkmp.domain.models.MimeType -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.getStringResource import io.github.ismoy.imagepickerkmp.presentation.ui.components.gallery.GalleryPickerLauncherContent +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.invalid_context_error +import org.jetbrains.compose.resources.stringResource @Suppress("ReturnCount","LongParameterList") @Composable @@ -27,8 +28,9 @@ actual fun GalleryPickerLauncher( ) { val context = LocalContext.current val activity = context + val invalidContextMsg = stringResource(Res.string.invalid_context_error) if (activity !is ComponentActivity) { - onError(Exception(getStringResource(StringResource.INVALID_CONTEXT_ERROR))) + onError(Exception(invalidContextMsg)) return } diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImageConfirmationViewWithCustomButtons.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImageConfirmationViewWithCustomButtons.kt index 46559cd..dcfbaf6 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImageConfirmationViewWithCustomButtons.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImageConfirmationViewWithCustomButtons.kt @@ -40,11 +40,17 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.platform.LocalConfiguration import coil3.compose.AsyncImage +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.accept_button +import imagepickerkmp.library.generated.resources.hd_quality_description +import imagepickerkmp.library.generated.resources.image_confirmation_title +import imagepickerkmp.library.generated.resources.preview_image_description +import imagepickerkmp.library.generated.resources.retry_button +import imagepickerkmp.library.generated.resources.sd_quality_description import io.github.ismoy.imagepickerkmp.domain.config.ImagePickerUiConstants import io.github.ismoy.imagepickerkmp.domain.config.UiConfig import io.github.ismoy.imagepickerkmp.domain.models.PhotoResult -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource +import org.jetbrains.compose.resources.stringResource @SuppressLint("ConfigurationScreenWidthHeight") @Suppress("EndOfSentenceFormat","LongMethod","FunctionNaming") @@ -102,7 +108,7 @@ fun ImageConfirmationViewWithCustomButtons( .aspectRatio(ImagePickerUiConstants.ConfirmationCardImageAspectRatio)){ AsyncImage( model = result.uri, - contentDescription = stringResource(StringResource.PREVIEW_IMAGE_DESCRIPTION), + contentDescription = stringResource(Res.string.preview_image_description), modifier = Modifier .fillMaxSize() .aspectRatio(ImagePickerUiConstants.ConfirmationCardImageAspectRatio) @@ -124,8 +130,8 @@ fun ImageConfirmationViewWithCustomButtons( ) { Icon( imageVector = if (isHD) Icons.Default.Hd else Icons.Default.Sd, - contentDescription = if (isHD) stringResource(StringResource.HD_QUALITY_DESCRIPTION) - else stringResource(StringResource.SD_QUALITY_DESCRIPTION), + contentDescription = if (isHD) stringResource(Res.string.hd_quality_description) + else stringResource(Res.string.sd_quality_description), tint = resolvedIconColor ) } @@ -141,7 +147,7 @@ fun ImageConfirmationViewWithCustomButtons( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = stringResource(StringResource.IMAGE_CONFIRMATION_TITLE), + text = stringResource(Res.string.image_confirmation_title), color = ImagePickerUiConstants.ConfirmationCardTitleColor, fontSize = ImagePickerUiConstants.ConfirmationCardTitleFontSize, fontWeight = FontWeight.Bold, @@ -168,13 +174,13 @@ fun ImageConfirmationViewWithCustomButtons( ) { Icon( imageVector = uiConfig.galleryIcon ?: Icons.Default.Refresh, - contentDescription = stringResource(StringResource.RETRY_BUTTON), + contentDescription = stringResource(Res.string.retry_button), tint = resolvedIconColor, modifier = Modifier .padding(end = ImagePickerUiConstants.ConfirmationCardButtonIconPadding) ) Text( - stringResource(StringResource.RETRY_BUTTON), color = resolvedIconColor, + stringResource(Res.string.retry_button), color = resolvedIconColor, fontWeight = ImagePickerUiConstants.ConfirmationCardButtonTextFontWeight) } Button( @@ -189,13 +195,13 @@ fun ImageConfirmationViewWithCustomButtons( ) { Icon( imageVector = Icons.Default.Check, - contentDescription = stringResource(StringResource.ACCEPT_BUTTON), + contentDescription = stringResource(Res.string.accept_button), tint = resolvedIconColor, modifier = Modifier .padding(end = ImagePickerUiConstants.ConfirmationCardButtonIconPadding) ) Text( - stringResource(StringResource.ACCEPT_BUTTON), + stringResource(Res.string.accept_button), color = resolvedIconColor, fontWeight = ImagePickerUiConstants.ConfirmationCardButtonTextFontWeight) } diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImagePickerLauncher.android.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImagePickerLauncher.android.kt index dc8b472..a0d78fd 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImagePickerLauncher.android.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/ImagePickerLauncher.android.kt @@ -1,4 +1,3 @@ - package io.github.ismoy.imagepickerkmp.presentation.ui.components import androidx.activity.ComponentActivity @@ -7,19 +6,20 @@ import androidx.compose.ui.platform.LocalContext import io.github.ismoy.imagepickerkmp.presentation.ui.screens.CameraCaptureView import io.github.ismoy.imagepickerkmp.domain.config.ImagePickerConfig import io.github.ismoy.imagepickerkmp.domain.config.CropConfig -import io.github.ismoy.imagepickerkmp.presentation.resources.getStringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource import io.github.ismoy.imagepickerkmp.presentation.ui.extensions.activity +import org.jetbrains.compose.resources.stringResource +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.invalid_context_error @Suppress("FunctionNaming") @Composable actual fun ImagePickerLauncher( config: ImagePickerConfig -){ +) { val context = LocalContext.current val activity = context.activity if (activity !is ComponentActivity) { - config.onError(Exception(getStringResource(StringResource.INVALID_CONTEXT_ERROR))) + config.onError(Exception(stringResource(Res.string.invalid_context_error))) return } CameraCaptureView( @@ -40,7 +40,7 @@ actual fun ImagePickerLauncher( } else if (config.enableCrop) { CropConfig( enabled = true, - circularCrop = true, + circularCrop = true, squareCrop = true ) } else { diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/LandscapeConfirmationLayout.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/LandscapeConfirmationLayout.kt index 3fb6ebc..60bc7cc 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/LandscapeConfirmationLayout.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/LandscapeConfirmationLayout.kt @@ -39,9 +39,14 @@ import coil3.compose.AsyncImage import io.github.ismoy.imagepickerkmp.domain.config.ImagePickerUiConstants import io.github.ismoy.imagepickerkmp.domain.config.UiConfig import io.github.ismoy.imagepickerkmp.domain.models.PhotoResult -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource - +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.accept_button +import imagepickerkmp.library.generated.resources.hd_quality_description +import imagepickerkmp.library.generated.resources.image_confirmation_title +import imagepickerkmp.library.generated.resources.preview_image_description +import imagepickerkmp.library.generated.resources.retry_button +import imagepickerkmp.library.generated.resources.sd_quality_description +import org.jetbrains.compose.resources.stringResource @Composable fun LandscapeConfirmationLayout( result: PhotoResult, @@ -71,7 +76,7 @@ import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource ) { AsyncImage( model = result.uri, - contentDescription = stringResource(StringResource.PREVIEW_IMAGE_DESCRIPTION), + contentDescription = stringResource(Res.string.preview_image_description), modifier = Modifier .fillMaxSize() .clip(RoundedCornerShape( @@ -96,8 +101,8 @@ import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource ) { Icon( imageVector = if (isHD) Icons.Default.Hd else Icons.Default.Sd, - contentDescription = if (isHD) stringResource(StringResource.HD_QUALITY_DESCRIPTION) - else stringResource(StringResource.SD_QUALITY_DESCRIPTION), + contentDescription = if (isHD) stringResource(Res.string.hd_quality_description) + else stringResource(Res.string.sd_quality_description), tint = resolvedIconColor ) } @@ -114,7 +119,7 @@ import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource verticalArrangement = Arrangement.Center ) { Text( - text = stringResource(StringResource.IMAGE_CONFIRMATION_TITLE), + text = stringResource(Res.string.image_confirmation_title), color = ImagePickerUiConstants.ConfirmationCardTitleColor, fontSize = ImagePickerUiConstants.ConfirmationCardTitleFontSize, fontWeight = FontWeight.Bold, @@ -141,12 +146,12 @@ import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource ) { Icon( imageVector = Icons.Default.Check, - contentDescription = stringResource(StringResource.ACCEPT_BUTTON), + contentDescription = stringResource(Res.string.accept_button), tint = resolvedIconColor, modifier = Modifier.padding(end = 8.dp) ) Text( - stringResource(StringResource.ACCEPT_BUTTON), + stringResource(Res.string.accept_button), color = resolvedIconColor, fontWeight = ImagePickerUiConstants.ConfirmationCardButtonTextFontWeight ) @@ -164,12 +169,12 @@ import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource ) { Icon( imageVector = uiConfig.galleryIcon ?: Icons.Default.Refresh, - contentDescription = stringResource(StringResource.RETRY_BUTTON), + contentDescription = stringResource(Res.string.retry_button), tint = resolvedIconColor, modifier = Modifier.padding(end = 8.dp) ) Text( - stringResource(StringResource.RETRY_BUTTON), + stringResource(Res.string.retry_button), color = resolvedIconColor, fontWeight = ImagePickerUiConstants.ConfirmationCardButtonTextFontWeight ) diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryOnlyPickerLaunchers.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryOnlyPickerLaunchers.kt index d9b017f..76f088f 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryOnlyPickerLaunchers.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryOnlyPickerLaunchers.kt @@ -3,15 +3,14 @@ package io.github.ismoy.imagepickerkmp.presentation.ui.components.gallery import android.content.Context import android.content.Intent import android.net.Uri -import android.provider.MediaStore +import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContract -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.gallery_selection_error import io.github.ismoy.imagepickerkmp.domain.models.CompressionLevel import io.github.ismoy.imagepickerkmp.domain.models.GalleryPhotoResult -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.getStringResource import io.github.ismoy.imagepickerkmp.presentation.ui.components.gallery.GalleryFileProcessor.processSelectedFile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -20,6 +19,7 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit +import org.jetbrains.compose.resources.stringResource class PickImageFromGallery : ActivityResultContract() { @@ -49,11 +49,11 @@ class PickMultipleImagesFromGallery : ActivityResultContract>( override fun parseResult(resultCode: Int, intent: Intent?): List { val uris = mutableListOf() - + if (resultCode != android.app.Activity.RESULT_OK) { - return uris + return uris } - + intent?.let { it.clipData?.let { clipData -> for (i in 0 until clipData.itemCount) { @@ -68,7 +68,7 @@ class PickMultipleImagesFromGallery : ActivityResultContract>( } } } - + return uris } } @@ -81,24 +81,28 @@ internal fun rememberGalleryOnlyPickerLauncher( onDismiss: () -> Unit, compressionLevel: CompressionLevel? = null, includeExif: Boolean = false -) = rememberLauncherForActivityResult( - contract = PickImageFromGallery() -) { uri: Uri? -> - if (uri != null) { - try { - CoroutineScope(Dispatchers.Main).launch { - val result = processSelectedFile(context, uri, compressionLevel, includeExif) - if (result != null) { - onPhotoSelected(result) - } else { - onError(Exception(getStringResource(StringResource.GALLERY_SELECTION_ERROR))) +): ManagedActivityResultLauncher { + val gallerySelectionErrorMsg = stringResource(Res.string.gallery_selection_error) + + return rememberLauncherForActivityResult( + contract = PickImageFromGallery() + ) { uri: Uri? -> + if (uri != null) { + try { + CoroutineScope(Dispatchers.Main).launch { + val result = processSelectedFile(context, uri, compressionLevel, includeExif) + if (result != null) { + onPhotoSelected(result) + } else { + onError(Exception(gallerySelectionErrorMsg)) + } } + } catch (e: Exception) { + onError(e) } - } catch (e: Exception) { - onError(e) + } else { + onDismiss() } - } else { - onDismiss() } } @@ -110,44 +114,48 @@ internal fun rememberGalleryOnlyMultiplePickerLauncher( onDismiss: () -> Unit, compressionLevel: CompressionLevel? = null, includeExif: Boolean = false -) = rememberLauncherForActivityResult( - contract = PickMultipleImagesFromGallery() -) { uris: List -> - if (uris.isNotEmpty()) { - try { - CoroutineScope(Dispatchers.Main).launch { - val semaphore = Semaphore(3) - val results = mutableListOf() - val errors = mutableListOf() - - val deferredResults = uris.map { uri -> - async(Dispatchers.IO) { - semaphore.withPermit { - try { - processSelectedFile(context, uri, compressionLevel, includeExif) - } catch (e: Exception) { - errors.add(e) - null +): ManagedActivityResultLauncher> { + val gallerySelectionErrorMsg = stringResource(Res.string.gallery_selection_error) + + return rememberLauncherForActivityResult( + contract = PickMultipleImagesFromGallery() + ) { uris: List -> + if (uris.isNotEmpty()) { + try { + CoroutineScope(Dispatchers.Main).launch { + val semaphore = Semaphore(3) + val results = mutableListOf() + val errors = mutableListOf() + + val deferredResults = uris.map { uri -> + async(Dispatchers.IO) { + semaphore.withPermit { + try { + processSelectedFile(context, uri, compressionLevel, includeExif) + } catch (e: Exception) { + errors.add(e) + null + } } } } + + val finalResults = deferredResults.awaitAll().filterNotNull() + results.addAll(finalResults) + + if (results.isNotEmpty()) { + onPhotosSelected(results) + } else if (errors.isNotEmpty()) { + onError(errors.first()) + } else { + onError(Exception(gallerySelectionErrorMsg)) + } } - - val finalResults = deferredResults.awaitAll().filterNotNull() - results.addAll(finalResults) - - if (results.isNotEmpty()) { - onPhotosSelected(results) - } else if (errors.isNotEmpty()) { - onError(errors.first()) - } else { - onError(Exception(getStringResource(StringResource.GALLERY_SELECTION_ERROR))) - } + } catch (e: Exception) { + onError(e) } - } catch (e: Exception) { - onError(e) + } else { + onDismiss() } - } else { - onDismiss() } } diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryPickerLaunchers.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryPickerLaunchers.kt index 2af1e1d..b0f99c3 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryPickerLaunchers.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/gallery/GalleryPickerLaunchers.kt @@ -2,13 +2,14 @@ package io.github.ismoy.imagepickerkmp.presentation.ui.components.gallery import android.content.Context import android.net.Uri +import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.gallery_selection_error import io.github.ismoy.imagepickerkmp.domain.models.CompressionLevel import io.github.ismoy.imagepickerkmp.domain.models.GalleryPhotoResult -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.getStringResource import io.github.ismoy.imagepickerkmp.presentation.ui.components.gallery.GalleryFileProcessor.processSelectedFile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -17,6 +18,8 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit +import org.jetbrains.compose.resources.getString +import org.jetbrains.compose.resources.stringResource @Composable @@ -27,24 +30,28 @@ internal fun rememberSinglePickerLauncher( onDismiss: () -> Unit, compressionLevel: CompressionLevel? = null, includeExif: Boolean = false -) = rememberLauncherForActivityResult( - contract = ActivityResultContracts.GetContent() -) { uri: Uri? -> - if (uri != null) { - try { - CoroutineScope(Dispatchers.Main).launch { - val result = processSelectedFile(context, uri, compressionLevel, includeExif) - if (result != null) { - onPhotoSelected(result) - } else { - onError(Exception(getStringResource(StringResource.GALLERY_SELECTION_ERROR))) +): ManagedActivityResultLauncher { + val gallerySelectionErrorMsg = stringResource(Res.string.gallery_selection_error) + + return rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent() + ) { uri: Uri? -> + if (uri != null) { + try { + CoroutineScope(Dispatchers.Main).launch { + val result = processSelectedFile(context, uri, compressionLevel, includeExif) + if (result != null) { + onPhotoSelected(result) + } else { + onError(Exception(gallerySelectionErrorMsg)) + } } + } catch (e: Exception) { + onError(e) } - } catch (e: Exception) { - onError(e) + } else { + onDismiss() } - } else { - onDismiss() } } @@ -65,7 +72,7 @@ internal fun rememberMultiplePickerLauncher( val semaphore = Semaphore(3) val results = mutableListOf() val errors = mutableListOf() - + val deferredResults = uris.map { uri -> async(Dispatchers.IO) { semaphore.withPermit { @@ -78,17 +85,17 @@ internal fun rememberMultiplePickerLauncher( } } } - + results.addAll(deferredResults.awaitAll().filterNotNull()) - + if (errors.isNotEmpty()) { errors.forEach { onError(it) } } - + if (results.isNotEmpty()) { onPhotosSelected(results) } else { - onError(Exception(getStringResource(StringResource.GALLERY_SELECTION_ERROR))) + onError(Exception(getString(Res.string.gallery_selection_error))) } } } catch (e: Exception) { diff --git a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/screens/CameraCaptureView.kt b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/screens/CameraCaptureView.kt index 29221e4..4fd7ad4 100644 --- a/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/screens/CameraCaptureView.kt +++ b/library/src/androidMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/screens/CameraCaptureView.kt @@ -21,8 +21,6 @@ import io.github.ismoy.imagepickerkmp.domain.models.GalleryPhotoResult import io.github.ismoy.imagepickerkmp.domain.models.PhotoResult import io.github.ismoy.imagepickerkmp.domain.utils.defaultCameraPermissionDialogConfig import io.github.ismoy.imagepickerkmp.domain.utils.playShutterSound -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.getStringResource import io.github.ismoy.imagepickerkmp.presentation.ui.components.CameraCapturePreview import io.github.ismoy.imagepickerkmp.presentation.ui.components.GalleryPickerLauncher import io.github.ismoy.imagepickerkmp.presentation.ui.components.ImageConfirmationViewWithCustomButtons @@ -30,8 +28,11 @@ import io.github.ismoy.imagepickerkmp.presentation.ui.components.ImageCropView import io.github.ismoy.imagepickerkmp.presentation.ui.components.RequestCameraPermission import io.github.ismoy.imagepickerkmp.presentation.ui.utils.rememberCameraManager import io.github.ismoy.imagepickerkmp.presentation.ui.utils.rememberImagePickerViewModel +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.camera_permission_permanently_denied +import org.jetbrains.compose.resources.stringResource -@Suppress("LongMethod","LongParameterList") +@Suppress("LongMethod", "LongParameterList") @Composable fun CameraCaptureView( activity: ComponentActivity, @@ -46,10 +47,10 @@ fun CameraCaptureView( var photoResult by remember { mutableStateOf(null) } var showCropView by remember { mutableStateOf(false) } var hasPermission by remember { mutableStateOf(false) } - + val imagePickerViewModel = rememberImagePickerViewModel() val cameraManager = rememberCameraManager(context, activity) - + if (cameraManager == null) { onError(Exception("Camera dependencies not available. Please check camera permissions and device capabilities.")) return @@ -89,9 +90,14 @@ fun CameraCaptureView( } ) } + cameraCaptureConfig.galleryConfig.allowMultiple && onPhotosSelected != null -> { GalleryPickerLauncher( - onPhotosSelected = { results: List -> onPhotosSelected(results) }, + onPhotosSelected = { results: List -> + onPhotosSelected( + results + ) + }, onError = { exception: Exception -> imagePickerViewModel.onError(exception) onError(exception) @@ -106,6 +112,7 @@ fun CameraCaptureView( includeExif = cameraCaptureConfig.galleryConfig.includeExif ) } + photoResult == null -> { CameraAndPreview( cameraCaptureConfig = cameraCaptureConfig, @@ -127,6 +134,7 @@ fun CameraCaptureView( } ) } + photoResult != null && !cameraCaptureConfig.permissionAndConfirmationConfig.skipConfirmation -> { ConfirmationView( photoResult = photoResult!!, @@ -153,13 +161,16 @@ private fun PermissionHandler( customDeniedDialog = customDeniedDialog, customSettingsDialog = customSettingsDialog ) + val cameraPermissionPermanentlyDeniedMsg = + stringResource(Res.string.camera_permission_permanently_denied) + if (customPermissionHandler != null) { customPermissionHandler(defaultConfig) } else { RequestCameraPermission( dialogConfig = dialogConfig, onPermissionPermanentlyDenied = { - onError(PhotoCaptureException(getStringResource(StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED))) + onError(PhotoCaptureException(cameraPermissionPermanentlyDeniedMsg)) }, onResult = { _: Boolean -> onPermissionGranted() }, customPermissionHandler = null diff --git a/library/src/androidMain/res/values-es/strings.xml b/library/src/commonMain/composeResources/values-es/strings.xml similarity index 100% rename from library/src/androidMain/res/values-es/strings.xml rename to library/src/commonMain/composeResources/values-es/strings.xml diff --git a/library/src/androidMain/res/values-fr/strings.xml b/library/src/commonMain/composeResources/values-fr/strings.xml similarity index 100% rename from library/src/androidMain/res/values-fr/strings.xml rename to library/src/commonMain/composeResources/values-fr/strings.xml diff --git a/library/src/commonMain/composeResources/values-ko/strings.xml b/library/src/commonMain/composeResources/values-ko/strings.xml new file mode 100644 index 0000000..32fd358 --- /dev/null +++ b/library/src/commonMain/composeResources/values-ko/strings.xml @@ -0,0 +1,48 @@ + + + + 카메라 권한 필요 + 사진을 촬영하려면 카메라 권한이 필요합니다. 설정에서 권한을 허용해주세요 + 설정 열기 + 카메라 권한 거부됨 + 사진을 촬영하려면 카메라 권한이 필요합니다. 권한을 허용해주세요 + 권한 허용 + 카메라 권한이 영구적으로 거부됨 + 저장소 권한 필요 + 갤러리에서 이미지를 선택하려면 저장소 접근 권한이 필요합니다. 권한을 허용해주세요. + 저장소 권한 거부됨 + 이미지를 선택하려면 저장소 권한이 필요합니다. 앱 설정에서 권한을 활성화해주세요. + 권한 허용 + 설정 열기 + + + 이 사진이 마음에 드시나요? + 확인 + 다시 찍기 + + + 옵션 선택 + 사진 촬영 + 갤러리에서 선택 + 취소 + + + 미리보기 + HD + SD + + + 잘못된 컨텍스트입니다. ComponentActivity여야 합니다 + 사진 촬영 실패 + 갤러리 선택 실패 + 권한 오류 발생 + + + 이미지 자르기 + 닫기 + 적용 + 사각형으로 자르기 + 원형으로 자르기 + 확대/축소 + 회전 + \ No newline at end of file diff --git a/library/src/androidMain/res/values/strings.xml b/library/src/commonMain/composeResources/values/strings.xml similarity index 82% rename from library/src/androidMain/res/values/strings.xml rename to library/src/commonMain/composeResources/values/strings.xml index 267bbcb..054e711 100644 --- a/library/src/androidMain/res/values/strings.xml +++ b/library/src/commonMain/composeResources/values/strings.xml @@ -19,21 +19,30 @@ Are you satisfied with the photo? Accept Retry - + Select option Take photo Select from gallery Cancel - + Preview HD SD - + Invalid context. Must be ComponentActivity Photo capture failed Gallery selection failed Permission error occurred + + + Crop Image + Cancel + Apply + Rectangular crop + Circular crop + Zoom + Rotation \ No newline at end of file diff --git a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/domain/config/PermissionConfig.kt b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/domain/config/PermissionConfig.kt index 46bc04f..d067e5e 100644 --- a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/domain/config/PermissionConfig.kt +++ b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/domain/config/PermissionConfig.kt @@ -1,8 +1,14 @@ package io.github.ismoy.imagepickerkmp.domain.config import androidx.compose.runtime.Composable -import io.github.ismoy.imagepickerkmp.presentation.resources.StringResource -import io.github.ismoy.imagepickerkmp.presentation.resources.stringResource +import org.jetbrains.compose.resources.stringResource +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.camera_permission_denied +import imagepickerkmp.library.generated.resources.camera_permission_denied_description +import imagepickerkmp.library.generated.resources.camera_permission_description +import imagepickerkmp.library.generated.resources.camera_permission_required +import imagepickerkmp.library.generated.resources.grant_permission +import imagepickerkmp.library.generated.resources.open_settings /** * Configuration for camera permission dialogs and messages. @@ -26,12 +32,12 @@ data class PermissionConfig( @Composable fun createLocalizedComposable(): PermissionConfig { return PermissionConfig( - titleDialogConfig = stringResource(StringResource.CAMERA_PERMISSION_REQUIRED), - descriptionDialogConfig = stringResource(StringResource.CAMERA_PERMISSION_DESCRIPTION), - btnDialogConfig = stringResource(StringResource.OPEN_SETTINGS), - titleDialogDenied = stringResource(StringResource.CAMERA_PERMISSION_DENIED), - descriptionDialogDenied = stringResource(StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION), - btnDialogDenied = stringResource(StringResource.GRANT_PERMISSION) + titleDialogConfig = stringResource(Res.string.camera_permission_required), + descriptionDialogConfig = stringResource(Res.string.camera_permission_description), + btnDialogConfig = stringResource(Res.string.open_settings), + titleDialogDenied = stringResource(Res.string.camera_permission_denied), + descriptionDialogDenied = stringResource(Res.string.camera_permission_denied_description), + btnDialogDenied = stringResource(Res.string.grant_permission) ) } } diff --git a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt index 0413ff1..5306266 100644 --- a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt +++ b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.kt @@ -1,6 +1,5 @@ package io.github.ismoy.imagepickerkmp.presentation.resources -import androidx.compose.runtime.Composable /** * Enum that defines all string resources used in the ImagePicker library. @@ -36,6 +35,3 @@ enum class StringResource { GALLERY_GRANT_PERMISSION, GALLERY_BTN_SETTINGS } - -@Composable -internal expect fun stringResource(id: StringResource): String diff --git a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropControlsPanel.kt b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropControlsPanel.kt index 07728db..8e4c029 100644 --- a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropControlsPanel.kt +++ b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropControlsPanel.kt @@ -26,7 +26,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.image_crop_view_circular_description +import imagepickerkmp.library.generated.resources.image_crop_view_rectangular_description +import imagepickerkmp.library.generated.resources.image_crop_view_rotation_label +import imagepickerkmp.library.generated.resources.image_crop_view_zoom_label import io.github.ismoy.imagepickerkmp.domain.config.CropConfig +import org.jetbrains.compose.resources.stringResource @Composable fun CropControlsPanel( @@ -41,7 +47,7 @@ fun CropControlsPanel( onRotationChange: (Float) -> Unit ) { val defaultPadding = 16.dp - + Column( modifier = Modifier .fillMaxWidth() @@ -72,7 +78,7 @@ fun CropControlsPanel( ) { Icon( imageVector = Icons.Filled.Crop, - contentDescription = "Rectangular crop", + contentDescription = stringResource(Res.string.image_crop_view_rectangular_description), tint = if (!isCircularCrop) Color.Black else Color.White, modifier = Modifier.size(16.dp) ) @@ -95,7 +101,7 @@ fun CropControlsPanel( ) { Icon( imageVector = Icons.Outlined.Circle, - contentDescription = "Circular crop", + contentDescription = stringResource(Res.string.image_crop_view_circular_description), tint = if (isCircularCrop) Color.Black else Color.White, modifier = Modifier.size(16.dp) ) @@ -116,7 +122,10 @@ fun CropControlsPanel( backgroundColor = if (aspectRatio == ratio && !isCircularCrop) Color.White else Color.Transparent, disabledBackgroundColor = Color.Gray.copy(alpha = 0.3f) ), - border = BorderStroke(1.dp, if (isCircularCrop) Color.Gray else Color.White), + border = BorderStroke( + 1.dp, + if (isCircularCrop) Color.Gray else Color.White + ), contentPadding = PaddingValues(horizontal = 6.dp, vertical = 4.dp) ) { Text( @@ -132,7 +141,12 @@ fun CropControlsPanel( } Column { - Text("Zoom", color = Color.White, fontSize = 14.sp, modifier = Modifier.padding(bottom = 4.dp)) + Text( + text = stringResource(Res.string.image_crop_view_zoom_label), + color = Color.White, + fontSize = 14.sp, + modifier = Modifier.padding(bottom = 4.dp) + ) Slider( value = zoomLevel, onValueChange = onZoomChange, @@ -147,7 +161,12 @@ fun CropControlsPanel( Spacer(modifier = Modifier.height(16.dp)) Column { - Text("Rotation", color = Color.White, fontSize = 14.sp, modifier = Modifier.padding(bottom = 4.dp)) + Text( + text = stringResource(Res.string.image_crop_view_rotation_label), + color = Color.White, + fontSize = 14.sp, + modifier = Modifier.padding(bottom = 4.dp) + ) Slider( value = rotationAngle, onValueChange = onRotationChange, diff --git a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropHeaderControls.kt b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropHeaderControls.kt index 5b9da9d..dc389c5 100644 --- a/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropHeaderControls.kt +++ b/library/src/commonMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/ui/components/CropHeaderControls.kt @@ -17,6 +17,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import imagepickerkmp.library.generated.resources.Res +import imagepickerkmp.library.generated.resources.image_crop_view_apply_description +import imagepickerkmp.library.generated.resources.image_crop_view_cancel_description +import imagepickerkmp.library.generated.resources.image_crop_view_title +import org.jetbrains.compose.resources.stringResource @Composable fun CropHeaderControls( @@ -35,13 +40,13 @@ fun CropHeaderControls( ) { Icon( imageVector = Icons.Default.Close, - contentDescription = "Cancel", + contentDescription = stringResource(Res.string.image_crop_view_cancel_description), tint = Color.White ) } Text( - text = "Crop Image", + text = stringResource(Res.string.image_crop_view_title), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 18.sp, @@ -54,7 +59,7 @@ fun CropHeaderControls( ) { Icon( imageVector = Icons.Default.Check, - contentDescription = "Apply", + contentDescription = stringResource(Res.string.image_crop_view_apply_description), tint = Color.White ) } diff --git a/library/src/iosMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/Strings.kt b/library/src/iosMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/Strings.kt deleted file mode 100644 index 255097c..0000000 --- a/library/src/iosMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/Strings.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.ismoy.imagepickerkmp.presentation.resources - -import androidx.compose.runtime.Composable -import kotlinx.cinterop.ExperimentalForeignApi -import platform.Foundation.NSBundle -import platform.Foundation.NSString -import platform.Foundation.NSUTF8StringEncoding -import platform.Foundation.stringWithContentsOfFile - -@OptIn(ExperimentalForeignApi::class) -private val defaultStringsMap: Map by lazy { - val bundle = NSBundle.mainBundle - val path = bundle.pathForResource("strings.default", "properties") ?: return@lazy emptyMap() - val content = NSString.stringWithContentsOfFile(path, NSUTF8StringEncoding, null) ?: return@lazy emptyMap() - - content - .lineSequence() - .filter { it.isNotBlank() && !it.trim().startsWith("#") } - .mapNotNull { line -> - val (key, value) = line.split("=", limit = 2).map { it.trim() } - if (key.isNotEmpty() && value.isNotEmpty()) key to value else null - }.toMap() -} - -@Composable -internal actual fun stringResource(id: StringResource): String = getDefaultString(id) - -private fun getDefaultString(id: StringResource): String { - val key = when (id) { - StringResource.GALLERY_PERMISSION_REQUIRED -> "gallery_permission_required" - StringResource.GALLERY_PERMISSION_DESCRIPTION -> "gallery_permission_description" - StringResource.GALLERY_PERMISSION_DENIED -> "gallery_permission_denied" - StringResource.GALLERY_PERMISSION_DENIED_DESCRIPTION -> "gallery_permission_denied_description" - StringResource.GALLERY_GRANT_PERMISSION -> "grant_gallery_permission" - StringResource.GALLERY_BTN_SETTINGS -> "gallery_btn_settings" - else -> id.name.lowercase() - } - return defaultStringsMap[key] ?: id.name.replace("_", " ").replaceFirstChar { it.uppercase() } -} diff --git a/library/src/iosMain/resources/en.lproj/Localizable.strings b/library/src/iosMain/resources/en.lproj/Localizable.strings deleted file mode 100644 index 0bf497c..0000000 --- a/library/src/iosMain/resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,30 +0,0 @@ -/* Camera Permissions */ -"camera_permission_required" = "Camera permission required"; -"camera_permission_description" = "Camera permission is required to capture photos. Please grant it in settings"; -"open_settings" = "Open settings"; -"camera_permission_denied" = "Camera permission denied"; -"camera_permission_denied_description" = "Camera permission is required to capture photos. Please grant the permissions"; -"grant_permission" = "Grant permission"; -"camera_permission_permanently_denied" = "Camera permission permanently denied"; - -/* Image Confirmation */ -"image_confirmation_title" = "Are you satisfied with the photo?"; -"accept_button" = "Accept"; -"retry_button" = "Retry"; - -/* Selection Dialogs */ -"select_option_dialog_title" = "Select option"; -"take_photo_option" = "Take photo"; -"select_from_gallery_option" = "Select from gallery"; -"cancel_option" = "Cancel"; - -/* Accessibility */ -"preview_image_description" = "Preview"; -"hd_quality_description" = "HD"; -"sd_quality_description" = "SD"; - -/* Errors */ -"invalid_context_error" = "Invalid context. Must be ComponentActivity"; -"photo_capture_error" = "Photo capture failed"; -"gallery_selection_error" = "Gallery selection failed"; -"permission_error" = "Permission error occurred"; \ No newline at end of file diff --git a/library/src/iosMain/resources/es.lproj/Localizable.strings b/library/src/iosMain/resources/es.lproj/Localizable.strings deleted file mode 100644 index 0548ff7..0000000 --- a/library/src/iosMain/resources/es.lproj/Localizable.strings +++ /dev/null @@ -1,30 +0,0 @@ -/* Camera Permissions */ -"camera_permission_required" = "Permiso de cámara requerido"; -"camera_permission_description" = "El permiso de cámara es necesario para capturar fotos. Por favor, concédelo en la configuración"; -"open_settings" = "Abrir configuración"; -"camera_permission_denied" = "Permiso de cámara denegado"; -"camera_permission_denied_description" = "El permiso de cámara es necesario para capturar fotos. Por favor, concede los permisos"; -"grant_permission" = "Conceder permiso"; -"camera_permission_permanently_denied" = "Permiso de cámara denegado permanentemente"; - -/* Image Confirmation */ -"image_confirmation_title" = "¿Estás satisfecho con la foto?"; -"accept_button" = "Aceptar"; -"retry_button" = "Reintentar"; - -/* Selection Dialogs */ -"select_option_dialog_title" = "Seleccionar opción"; -"take_photo_option" = "Tomar foto"; -"select_from_gallery_option" = "Seleccionar de galería"; -"cancel_option" = "Cancelar"; - -/* Accessibility */ -"preview_image_description" = "Vista previa"; -"hd_quality_description" = "HD"; -"sd_quality_description" = "SD"; - -/* Errors */ -"invalid_context_error" = "Contexto inválido. Debe ser ComponentActivity"; -"photo_capture_error" = "Error al capturar la foto"; -"gallery_selection_error" = "Error al seleccionar de la galería"; -"permission_error" = "Error de permisos"; \ No newline at end of file diff --git a/library/src/iosMain/resources/fr.lproj/Localizable.strings b/library/src/iosMain/resources/fr.lproj/Localizable.strings deleted file mode 100644 index f7add27..0000000 --- a/library/src/iosMain/resources/fr.lproj/Localizable.strings +++ /dev/null @@ -1,30 +0,0 @@ -/* Permissions d'appareil photo */ -"camera_permission_required" = "Permission d'appareil photo requise"; -"camera_permission_description" = "Cette application a besoin d'accéder à votre appareil photo pour prendre des photos"; -"open_settings" = "Ouvrir les paramètres"; -"camera_permission_denied" = "Permission d'appareil photo refusée"; -"camera_permission_denied_description" = "Pour utiliser cette fonctionnalité, vous devez autoriser l'accès à l'appareil photo dans les paramètres"; -"grant_permission" = "Accorder la permission"; -"camera_permission_permanently_denied" = "Permission d'appareil photo définitivement refusée"; - -/* Confirmation d'image */ -"image_confirmation_title" = "Confirmer l'image"; -"accept_button" = "Accepter"; -"retry_button" = "Réessayer"; - -/* Dialogues de sélection */ -"select_option_dialog_title" = "Sélectionner une option"; -"take_photo_option" = "Prendre une photo"; -"select_from_gallery_option" = "Sélectionner depuis la galerie"; -"cancel_option" = "Annuler"; - -/* Accessibilité */ -"preview_image_description" = "Aperçu de l'image capturée"; -"hd_quality_description" = "Qualité haute définition"; -"sd_quality_description" = "Qualité standard"; - -/* Erreurs */ -"invalid_context_error" = "Contexte invalide"; -"photo_capture_error" = "Erreur lors de la capture de la photo"; -"gallery_selection_error" = "Erreur lors de la sélection depuis la galerie"; -"permission_error" = "Erreur de permission"; \ No newline at end of file diff --git a/library/src/jsMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.js.kt b/library/src/jsMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.js.kt deleted file mode 100644 index e7474f9..0000000 --- a/library/src/jsMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.js.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.ismoy.imagepickerkmp.presentation.resources - -import androidx.compose.runtime.Composable - -@Composable -internal actual fun stringResource(id: StringResource): String { - return when (id) { - StringResource.CAMERA_PERMISSION_REQUIRED -> "Camera Permission Required" - StringResource.CAMERA_PERMISSION_DESCRIPTION -> "This app needs camera access to take photos. Please grant camera permission in your browser." - StringResource.OPEN_SETTINGS -> "Open Settings" - StringResource.CAMERA_PERMISSION_DENIED -> "Permission Denied" - StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION -> "Camera permission was denied" - StringResource.GRANT_PERMISSION -> "Grant Permission" - StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED -> "Camera Permission Permanently Denied" - StringResource.IMAGE_CONFIRMATION_TITLE -> "Confirm Image" - StringResource.ACCEPT_BUTTON -> "Accept" - StringResource.RETRY_BUTTON -> "Retry" - StringResource.SELECT_OPTION_DIALOG_TITLE -> "Select Option" - StringResource.TAKE_PHOTO_OPTION -> "Take Photo" - StringResource.SELECT_FROM_GALLERY_OPTION -> "Select from Gallery" - StringResource.CANCEL_OPTION -> "Cancel" - StringResource.PREVIEW_IMAGE_DESCRIPTION -> "Preview Image" - StringResource.HD_QUALITY_DESCRIPTION -> "HD Quality" - StringResource.SD_QUALITY_DESCRIPTION -> "SD Quality" - StringResource.INVALID_CONTEXT_ERROR -> "Invalid Context Error" - StringResource.PHOTO_CAPTURE_ERROR -> "Photo Capture Error" - StringResource.GALLERY_SELECTION_ERROR -> "Gallery Selection Error" - StringResource.PERMISSION_ERROR -> "Permission Error" - StringResource.GALLERY_PERMISSION_REQUIRED -> "Gallery Permission Required" - StringResource.GALLERY_PERMISSION_DESCRIPTION -> "This app needs gallery access to select photos" - StringResource.GALLERY_PERMISSION_DENIED -> "Gallery Permission Denied" - StringResource.GALLERY_PERMISSION_DENIED_DESCRIPTION -> "Gallery permission was denied" - StringResource.GALLERY_GRANT_PERMISSION -> "Grant Gallery Permission" - StringResource.GALLERY_BTN_SETTINGS -> "Gallery Settings" - } -} diff --git a/library/src/jvmMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.jvm.kt b/library/src/jvmMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.jvm.kt deleted file mode 100644 index 2a9e35e..0000000 --- a/library/src/jvmMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.jvm.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.ismoy.imagepickerkmp.presentation.resources - -import androidx.compose.runtime.Composable - -/** - * Desktop implementation of stringResource. - * Returns a simple English translation for each string resource. - */ -@Composable -internal actual fun stringResource(id: StringResource): String { - return when (id) { - StringResource.CAMERA_PERMISSION_REQUIRED -> "Camera Permission Required" - StringResource.CAMERA_PERMISSION_DESCRIPTION -> "This app needs camera permission to take photos" - StringResource.OPEN_SETTINGS -> "Open Settings" - StringResource.CAMERA_PERMISSION_DENIED -> "Camera Permission Denied" - StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION -> "Camera permission was denied. This app needs camera permission to take photos." - StringResource.GRANT_PERMISSION -> "Grant Permission" - StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED -> "Camera permission was permanently denied. Please enable it in settings." - StringResource.IMAGE_CONFIRMATION_TITLE -> "Use this image?" - StringResource.ACCEPT_BUTTON -> "Use Image" - StringResource.RETRY_BUTTON -> "Try Again" - StringResource.SELECT_OPTION_DIALOG_TITLE -> "Select Image Source" - StringResource.TAKE_PHOTO_OPTION -> "Take Photo" - StringResource.SELECT_FROM_GALLERY_OPTION -> "Choose from Gallery" - StringResource.CANCEL_OPTION -> "Cancel" - StringResource.PREVIEW_IMAGE_DESCRIPTION -> "Preview of captured image" - StringResource.HD_QUALITY_DESCRIPTION -> "HD Quality" - StringResource.SD_QUALITY_DESCRIPTION -> "Standard Quality" - StringResource.INVALID_CONTEXT_ERROR -> "Invalid context provided" - StringResource.PHOTO_CAPTURE_ERROR -> "Failed to capture image" - StringResource.GALLERY_SELECTION_ERROR -> "Failed to select image from gallery" - StringResource.PERMISSION_ERROR -> "Permission error" - StringResource.GALLERY_PERMISSION_REQUIRED -> "Gallery Permission Required" - StringResource.GALLERY_PERMISSION_DESCRIPTION -> "This app needs gallery permission to access photos" - StringResource.GALLERY_PERMISSION_DENIED -> "Gallery Permission Denied" - StringResource.GALLERY_PERMISSION_DENIED_DESCRIPTION -> "Gallery permission was denied. This app needs gallery permission to access photos." - StringResource.GALLERY_GRANT_PERMISSION -> "Grant Gallery Permission" - StringResource.GALLERY_BTN_SETTINGS -> "Settings" - } -} diff --git a/library/src/wasmJsMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.wasmJs.kt b/library/src/wasmJsMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.wasmJs.kt deleted file mode 100644 index 8eb06bf..0000000 --- a/library/src/wasmJsMain/kotlin/io/github/ismoy/imagepickerkmp/presentation/resources/StringResource.wasmJs.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.ismoy.imagepickerkmp.presentation.resources - -import androidx.compose.runtime.Composable - -/** - * WASM platform implementation of stringResource. - * Provides English fallback strings for WASM platform. - */ -@Composable -internal actual fun stringResource(id: StringResource): String { - return when (id) { - StringResource.CAMERA_PERMISSION_REQUIRED -> "Camera Permission Required" - StringResource.CAMERA_PERMISSION_DESCRIPTION -> "This app needs access to your camera to take photos" - StringResource.OPEN_SETTINGS -> "Open Settings" - StringResource.CAMERA_PERMISSION_DENIED -> "Camera Permission Denied" - StringResource.CAMERA_PERMISSION_DENIED_DESCRIPTION -> "Camera access was denied. You can enable it in settings." - StringResource.GRANT_PERMISSION -> "Grant Permission" - StringResource.CAMERA_PERMISSION_PERMANENTLY_DENIED -> "Camera Permission Permanently Denied" - StringResource.IMAGE_CONFIRMATION_TITLE -> "Are you satisfied with the photo?" - StringResource.ACCEPT_BUTTON -> "Accept" - StringResource.RETRY_BUTTON -> "Retry" - StringResource.SELECT_OPTION_DIALOG_TITLE -> "Select option" - StringResource.TAKE_PHOTO_OPTION -> "Take photo" - StringResource.SELECT_FROM_GALLERY_OPTION -> "Select from gallery" - StringResource.CANCEL_OPTION -> "Cancel" - StringResource.PREVIEW_IMAGE_DESCRIPTION -> "Preview" - StringResource.HD_QUALITY_DESCRIPTION -> "HD" - StringResource.SD_QUALITY_DESCRIPTION -> "SD" - StringResource.INVALID_CONTEXT_ERROR -> "Invalid context error" - StringResource.PHOTO_CAPTURE_ERROR -> "Photo capture failed" - StringResource.GALLERY_SELECTION_ERROR -> "Gallery selection failed" - StringResource.PERMISSION_ERROR -> "Permission error" - StringResource.GALLERY_PERMISSION_REQUIRED -> "Gallery Permission Required" - StringResource.GALLERY_PERMISSION_DESCRIPTION -> "This app needs access to your gallery to select photos" - StringResource.GALLERY_PERMISSION_DENIED -> "Gallery Permission Denied" - StringResource.GALLERY_PERMISSION_DENIED_DESCRIPTION -> "Gallery access was denied. You can enable it in settings." - StringResource.GALLERY_GRANT_PERMISSION -> "Grant Permission" - StringResource.GALLERY_BTN_SETTINGS -> "Settings" - } -}