diff --git a/app/src/foss/AndroidManifest.xml b/app/src/foss/AndroidManifest.xml index 5e2a44ae..6e78f8d0 100755 --- a/app/src/foss/AndroidManifest.xml +++ b/app/src/foss/AndroidManifest.xml @@ -7,7 +7,6 @@ android:required="false" /> - diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index 5e2a44ae..6e78f8d0 100755 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -7,7 +7,6 @@ android:required="false" /> - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4aac9eb0..857bdb8d 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -59,5 +59,15 @@ android:name="autoStoreLocales" android:value="true" /> + + + + + + + diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml index 2f3afb0d..532c7d27 100644 --- a/app/src/main/res/xml/file_provider_paths.xml +++ b/app/src/main/res/xml/file_provider_paths.xml @@ -12,6 +12,8 @@ name="folder_work" path="work/" /> + + diff --git a/app/src/playstore/AndroidManifest.xml b/app/src/playstore/AndroidManifest.xml index bb6cecbe..481f8223 100755 --- a/app/src/playstore/AndroidManifest.xml +++ b/app/src/playstore/AndroidManifest.xml @@ -7,7 +7,6 @@ android:required="false" /> - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6186b7c3..f79c2d2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,6 @@ crop = "0.1.1" mvi = "1.0.2" preferences = "1.0.1" dayNightSwitch = "1.0.0" -imagepicker = "v2.0.3" catppuccin = "0.1.2" turbine = "1.2.0" roboelectric = "4.13" @@ -108,7 +107,6 @@ compose-gestures = { group = "com.github.SmartToolFactory", name = "Compose-Exte compose-crop = { group = "io.github.mr0xf00", name = "easycrop", version.ref = "crop" } shifthackz-mvi = { group = "com.github.ShiftHackZ", name = "AndroidCoreMVI", version.ref = "mvi" } shifthackz-preferences = { group = "com.github.ShiftHackZ", name = "AndroidPreferences", version.ref = "preferences" } -shifthackz-imagepicker = { group = "com.github.ShiftHackZ", name = "ImagePicker", version.ref = "imagepicker" } shifthackz-daynightswitch = { group = "com.github.ShiftHackZ", name = "DayNightSwitch", version.ref = "dayNightSwitch" } shifthackz-catppuccin-legacy = { group = "com.github.ShiftHackZ.Catppuccin-Android-Library", name = "palette-legacy", version.ref = "catppuccin" } shifthackz-catppuccin-compose = { group = "com.github.ShiftHackZ.Catppuccin-Android-Library", name = "compose", version.ref = "catppuccin" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 254bb6ec..4fec26cb 100755 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { implementation(libs.rx.kotlin) implementation(libs.rx.android) - implementation(libs.shifthackz.imagepicker) implementation(libs.shifthackz.daynightswitch) implementation(libs.shifthackz.catppuccin.compose) implementation(libs.shifthackz.catppuccin.splash) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt index e496b190..8adef7d4 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/core/GenerationMviIntent.kt @@ -11,7 +11,6 @@ import com.shifthackz.aisdv1.domain.entity.StabilityAiStylePreset import com.shifthackz.aisdv1.presentation.model.Modal import com.shifthackz.aisdv1.presentation.screen.drawer.DrawerIntent import com.shifthackz.android.core.mvi.MviIntent -import com.shz.imagepicker.imagepicker.model.PickedResult sealed interface GenerationMviIntent : MviIntent { @@ -109,7 +108,7 @@ sealed interface ImageToImageIntent : GenerationMviIntent { data class UpdateImage(val bitmap: Bitmap) : ImageToImageIntent - data class CropImage(val result: PickedResult) : ImageToImageIntent + data class CropImage(val bitmap: Bitmap) : ImageToImageIntent enum class Pick : ImageToImageIntent { Camera, Gallery diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt index 421a98fd..a069c396 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageScreen.kt @@ -2,6 +2,9 @@ package com.shifthackz.aisdv1.presentation.screen.img2img +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -53,6 +56,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.FileProvider import com.shifthackz.aisdv1.core.common.file.FileProviderDescriptor import com.shifthackz.aisdv1.core.common.math.roundTo import com.shifthackz.aisdv1.core.model.UiText @@ -70,13 +74,14 @@ import com.shifthackz.aisdv1.presentation.screen.inpaint.components.InPaintCompo import com.shifthackz.aisdv1.presentation.theme.sliderColors import com.shifthackz.aisdv1.presentation.utils.Constants.DENOISING_STRENGTH_MAX import com.shifthackz.aisdv1.presentation.utils.Constants.DENOISING_STRENGTH_MIN +import com.shifthackz.aisdv1.presentation.utils.PermissionUtil +import com.shifthackz.aisdv1.presentation.utils.uriToBitmap import com.shifthackz.aisdv1.presentation.widget.input.GenerationInputForm import com.shifthackz.aisdv1.presentation.widget.toolbar.GenerationBottomToolbar import com.shifthackz.aisdv1.presentation.widget.work.BackgroundWorkWidget -import com.shz.imagepicker.imagepicker.ImagePicker -import com.shz.imagepicker.imagepicker.model.GalleryPicker import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject +import java.io.File import com.shifthackz.aisdv1.core.localization.R as LocalizationR @Composable @@ -84,19 +89,58 @@ fun ImageToImageScreen() { val context = LocalContext.current val viewModel = koinViewModel() val fileProviderDescriptor: FileProviderDescriptor = koinInject() + + val cameraFile = File(context.cacheDir, "camera.jpg").apply { + createNewFile() + deleteOnExit() + } + + val cameraUri = FileProvider.getUriForFile( + context, + fileProviderDescriptor.providerPath, + cameraFile, + ) + + val cameraPicker = rememberLauncherForActivityResult( + ActivityResultContracts.TakePicture(), + ) { success -> + if (!success) return@rememberLauncherForActivityResult + val bitmap = uriToBitmap(context, cameraUri) ?: return@rememberLauncherForActivityResult + viewModel.processIntent(ImageToImageIntent.CropImage(bitmap)) + } + + val cameraPermission = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (!isGranted) return@rememberLauncherForActivityResult + cameraPicker.launch(cameraUri) + } + + val mediaPicker = rememberLauncherForActivityResult( + ActivityResultContracts.PickVisualMedia(), + ) { uri -> + val bitmap = + uri?.let { uriToBitmap(context, it) } ?: return@rememberLauncherForActivityResult + viewModel.processIntent(ImageToImageIntent.CropImage(bitmap)) + } + MviComponent( viewModel = viewModel, processEffect = { effect -> - ImagePicker.Builder(fileProviderDescriptor.providerPath) { result -> - viewModel.processIntent(ImageToImageIntent.CropImage(result)) + when (effect) { + ImageToImageEffect.GalleryPicker -> { + val request = PickVisualMediaRequest( + ActivityResultContracts.PickVisualMedia.ImageOnly, + ) + mediaPicker.launch(request) + } + + ImageToImageEffect.CameraPicker -> { + if (PermissionUtil.checkCameraPermission(context, cameraPermission::launch)) { + cameraPicker.launch(cameraUri) + } + } } - .useGallery(effect == ImageToImageEffect.GalleryPicker) - .useCamera(effect == ImageToImageEffect.CameraPicker) - .autoRotate(effect == ImageToImageEffect.GalleryPicker) - .multipleSelection(false) - .galleryPicker(GalleryPicker.NATIVE) - .build() - .launch(context) }, ) { state, intentHandler -> ScreenContent( @@ -111,7 +155,7 @@ fun ImageToImageScreen() { private fun ScreenContent( modifier: Modifier = Modifier, state: ImageToImageState, - processIntent: (GenerationMviIntent) -> Unit = {} + processIntent: (GenerationMviIntent) -> Unit = {}, ) { val promptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } val negativePromptChipTextFieldState = remember { mutableStateOf(TextFieldValue()) } @@ -340,7 +384,7 @@ private fun ScreenContent( ServerSource.OPEN_AI -> LocalizationR.string.action_change_configuration else -> LocalizationR.string.action_generate - } + }, ), color = LocalContentColor.current, ) @@ -499,7 +543,7 @@ private fun ImagePickButtonBox( id = when (buttonType) { ImagePickButton.PHOTO -> LocalizationR.string.action_image_picker_gallery ImagePickButton.CAMERA -> LocalizationR.string.action_image_picker_camera - } + }, ), fontSize = 17.sp, ) diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt index 5ffb6212..dde779a5 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/screen/img2img/ImageToImageViewModel.kt @@ -32,7 +32,6 @@ import com.shifthackz.aisdv1.presentation.model.Modal import com.shifthackz.aisdv1.presentation.navigation.router.drawer.DrawerRouter import com.shifthackz.aisdv1.presentation.navigation.router.main.MainRouter import com.shifthackz.aisdv1.presentation.screen.inpaint.InPaintStateProducer -import com.shz.imagepicker.imagepicker.model.PickedResult import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.kotlin.subscribeBy @@ -141,12 +140,8 @@ class ImageToImageViewModel( ImageToImageIntent.Pick.Gallery -> emitEffect(ImageToImageEffect.GalleryPicker) - is ImageToImageIntent.CropImage -> when (intent.result) { - is PickedResult.Single -> updateState { - it.copy(screenModal = Modal.Image.Crop(intent.result.image.bitmap)) - } - - else -> Unit + is ImageToImageIntent.CropImage -> updateState { + it.copy(screenModal = Modal.Image.Crop(intent.bitmap)) } is ImageToImageIntent.UpdateImage -> updateState { diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt index c001bcb6..9e0b83ed 100644 --- a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/PermissionUtil.kt @@ -11,7 +11,7 @@ object PermissionUtil { fun checkStoragePermission( context: Context, onLaunch: (missingPermissions: Array) -> Unit = {}, - ): Boolean { + ): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return false } @@ -43,4 +43,19 @@ object PermissionUtil { } return true } + + fun checkCameraPermission( + context: Context, + onLaunch: (missingPermission: String) -> Unit, + ): Boolean { + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) != PackageManager.PERMISSION_GRANTED + ) { + onLaunch(Manifest.permission.CAMERA) + return false + } + return true + } } diff --git a/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/UriToBitmap.kt b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/UriToBitmap.kt new file mode 100644 index 00000000..56930b01 --- /dev/null +++ b/presentation/src/main/java/com/shifthackz/aisdv1/presentation/utils/UriToBitmap.kt @@ -0,0 +1,15 @@ +package com.shifthackz.aisdv1.presentation.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import com.shifthackz.aisdv1.core.common.log.errorLog + +fun uriToBitmap(context: Context, uri: Uri): Bitmap? = try { + val inputStream = context.contentResolver.openInputStream(uri) + BitmapFactory.decodeStream(inputStream).also { inputStream?.close() } +} catch (e: Exception) { + errorLog("UrlToBitmap", e) + null +}