@@ -5,6 +5,9 @@ import android.app.Activity
55import android.content.Context
66import android.content.Intent
77import android.content.pm.PackageManager
8+ import android.graphics.Bitmap
9+ import android.graphics.BitmapFactory
10+ import android.net.Uri
811import android.provider.MediaStore
912import androidx.activity.compose.rememberLauncherForActivityResult
1013import androidx.activity.result.PickVisualMediaRequest
@@ -23,22 +26,39 @@ import androidx.compose.material3.AlertDialog
2326import androidx.compose.material3.Text
2427import androidx.compose.material3.TextButton
2528import androidx.compose.runtime.Composable
29+ import androidx.compose.runtime.LaunchedEffect
2630import androidx.compose.runtime.MutableState
2731import androidx.compose.runtime.mutableStateOf
2832import androidx.compose.runtime.remember
33+ import androidx.compose.runtime.rememberCoroutineScope
2934import androidx.compose.ui.Modifier
35+ import androidx.compose.ui.graphics.ImageBitmap
36+ import androidx.compose.ui.graphics.asImageBitmap
3037import androidx.compose.ui.platform.LocalContext
3138import androidx.compose.ui.unit.dp
3239import androidx.compose.ui.window.DialogProperties
3340import androidx.core.content.ContextCompat
41+ import androidx.core.net.toUri
42+ import com.mr0xf00.easycrop.CropError
43+ import com.mr0xf00.easycrop.CropResult
44+ import com.mr0xf00.easycrop.CropperStyle
45+ import com.mr0xf00.easycrop.ImageCropper
46+ import com.mr0xf00.easycrop.crop
47+ import com.mr0xf00.easycrop.rememberImageCropper
48+ import com.mr0xf00.easycrop.ui.ImageCropperDialog
3449import com.streamliners.pickers.media.FromGalleryType.VisualMediaPicker
3550import com.streamliners.pickers.media.MediaType.Image
3651import com.streamliners.pickers.media.MediaType.Video
3752import com.streamliners.pickers.media.comp.OptionButton
3853import com.streamliners.pickers.media.util.VideoMetadataExtractor
3954import com.streamliners.pickers.media.util.createFile
4055import com.streamliners.pickers.media.util.getUri
56+ import com.streamliners.pickers.media.util.saveBitmapToFile
4157import com.streamliners.utils.safeLet
58+ import kotlinx.coroutines.CoroutineScope
59+ import kotlinx.coroutines.launch
60+ import java.io.File
61+
4262
4363@Composable
4464fun MediaPickerDialog (
@@ -47,6 +67,13 @@ fun MediaPickerDialog(
4767) {
4868 val data = state.value as ? MediaPickerDialogState .Visible ? : return
4969
70+ LaunchedEffect (key1 = Unit ) {
71+ if (data.cropParams is MediaPickerCropParams .Enabled ) {
72+ if (data.type == Video ) error(" cropParams are not allowed for MediaType.Video" )
73+ if (data.allowMultiple) error(" cropParams are not allowed when allowMultiple = true" )
74+ }
75+ }
76+
5077 val context = LocalContext .current
5178
5279 val cameraPermissionIsGranted = {
@@ -55,6 +82,8 @@ fun MediaPickerDialog(
5582 ) == PackageManager .PERMISSION_GRANTED
5683 }
5784
85+ val imageCropper = rememberImageCropper()
86+
5887 AlertDialog (
5988 modifier = Modifier
6089 .padding(28 .dp)
@@ -79,12 +108,12 @@ fun MediaPickerDialog(
79108 ) {
80109 FromCameraButton (
81110 modifier = Modifier .weight(1f ),
82- state, data, authority, cameraPermissionIsGranted
111+ state, data, authority, cameraPermissionIsGranted, imageCropper
83112 )
84113
85114 FromGalleryButton (
86115 modifier = Modifier .weight(1f ),
87- state, data
116+ state, data, imageCropper, authority
88117 )
89118 }
90119
@@ -99,6 +128,20 @@ fun MediaPickerDialog(
99128 }
100129 }
101130 )
131+
132+ imageCropper.cropState?.let {
133+
134+ ImageCropperDialog (
135+ state = it,
136+ style = CropperStyle (
137+ autoZoom = false ,
138+ guidelines = null
139+ ),
140+ showAspectRatioSelectionButton = (data.cropParams as ? MediaPickerCropParams .Enabled )?.showAspectRatioSelectionButton ? : true ,
141+ showShapeCropButton = (data.cropParams as ? MediaPickerCropParams .Enabled )?.showAspectRatioSelectionButton ? : true ,
142+ lockAspectRatio = (data.cropParams as ? MediaPickerCropParams .Enabled )?.lockAspectRatio
143+ )
144+ }
102145}
103146
104147@Composable
@@ -107,25 +150,41 @@ fun FromCameraButton(
107150 state : MutableState <MediaPickerDialogState >,
108151 data : MediaPickerDialogState .Visible ,
109152 authority : String ,
110- cameraPermissionIsGranted : () -> Boolean
153+ cameraPermissionIsGranted : () -> Boolean ,
154+ imageCropper : ImageCropper
111155) {
112156 val context = LocalContext .current
113157
114158 val filePath = remember { mutableStateOf<String ?>(null ) }
115159 val fileUri = remember { mutableStateOf<String ?>(null ) }
160+ val scope = rememberCoroutineScope()
116161
117162 val cameraLauncher = rememberLauncherForActivityResult(
118163 contract = ActivityResultContracts .StartActivityForResult (),
119164 onResult = { result ->
120165 if (result.resultCode == Activity .RESULT_OK ) {
121166 safeLet(filePath.value, fileUri.value) { path, uri ->
122- data.callback {
123- listOf (
124- when (data.type) {
125- Image -> PickedMedia .Image (uri, path)
126- Video -> processVideo(context, uri, path)
167+
168+ when (data.type) {
169+ Image -> {
170+ showImageCropperIfRequired(
171+ data,
172+ PickedMedia .Image (uri, path),
173+ imageCropper,
174+ context,
175+ scope,
176+ authority
177+ ) {
178+ data.callback { listOf (it) }
179+ }
180+ }
181+ Video -> {
182+ data.callback {
183+ listOf (
184+ processVideo(context, uri, path)
185+ )
127186 }
128- )
187+ }
129188 }
130189 }
131190 state.dismiss()
@@ -187,10 +246,14 @@ fun FromCameraButton(
187246fun FromGalleryButton (
188247 modifier : Modifier ,
189248 state : MutableState <MediaPickerDialogState >,
190- data : MediaPickerDialogState .Visible
249+ data : MediaPickerDialogState .Visible ,
250+ imageCropper : ImageCropper ,
251+ authority : String
191252) {
192253 val context = LocalContext .current
193254
255+ val scope = rememberCoroutineScope()
256+
194257 val documentPickerLauncher = rememberLauncherForActivityResult(
195258 contract = ActivityResultContracts .StartActivityForResult (),
196259 onResult = { result ->
@@ -208,11 +271,32 @@ fun FromGalleryButton(
208271 }
209272 }.filterNotNull()
210273
211- data.callback {
212- items.map { uri ->
213- when (data.type) {
214- Image -> PickedMedia .Image (uri.toString())
215- Video -> processVideo(context, uri.toString())
274+ when (data.type) {
275+ Image -> {
276+ if (data.allowMultiple) {
277+ data.callback {
278+ items.map { uri ->
279+ PickedMedia .Image (uri.toString())
280+ }
281+ }
282+ } else {
283+ showImageCropperIfRequired(
284+ data,
285+ PickedMedia .Image (items.first().toString()),
286+ imageCropper,
287+ context,
288+ scope,
289+ authority
290+ ) {
291+ data.callback { listOf (it) }
292+ }
293+ }
294+ }
295+ Video -> {
296+ data.callback {
297+ items.map { uri ->
298+ processVideo(context, uri.toString())
299+ }
216300 }
217301 }
218302 }
@@ -230,13 +314,27 @@ fun FromGalleryButton(
230314 uri,
231315 Intent .FLAG_GRANT_READ_URI_PERMISSION
232316 )
233- data.callback {
234- listOf (
235- when (data.type) {
236- Image -> PickedMedia .Image (uri.toString())
237- Video -> processVideo(context, uri.toString())
317+
318+ when (data.type) {
319+ Image -> {
320+ showImageCropperIfRequired(
321+ data,
322+ PickedMedia .Image (uri.toString()),
323+ imageCropper,
324+ context,
325+ scope,
326+ authority
327+ ) {
328+ data.callback { listOf (it) }
238329 }
239- )
330+ }
331+ Video -> {
332+ data.callback {
333+ listOf (
334+ processVideo(context, uri.toString())
335+ )
336+ }
337+ }
240338 }
241339 }
242340 state.dismiss()
@@ -306,4 +404,40 @@ suspend fun processVideo(
306404 duration = VideoMetadataExtractor .getDuration(context, uri),
307405 thumbnailUri = VideoMetadataExtractor .getThumbnailUri(context, uri)
308406 )
407+ }
408+
409+ fun showImageCropperIfRequired (
410+ data : MediaPickerDialogState .Visible ,
411+ image : PickedMedia .Image ,
412+ imageCropper : ImageCropper ,
413+ context : Context ,
414+ scope : CoroutineScope ,
415+ authority : String ,
416+ onReady : (PickedMedia .Image ) -> Unit
417+ ) {
418+ data.cropParams as ? MediaPickerCropParams .Enabled ? : run {
419+ onReady(image)
420+ return
421+ }
422+
423+ scope.launch {
424+
425+ // val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, Uri.parse(image.uri))
426+
427+ when (
428+ // val result = imageCropper.crop(bmp = bitmap.asImageBitmap())
429+ val result = imageCropper.crop(image.uri.toUri(), context, cacheBeforeUse = false )
430+ ) {
431+ CropResult .Cancelled -> {
432+ error(" Crop cancelled" )
433+ }
434+ is CropError -> {
435+ error(" Crop error : ${result.name} " )
436+ }
437+ is CropResult .Success -> {
438+ val croppedImageUri = saveBitmapToFile(context, result.bitmap, authority)
439+ onReady(PickedMedia .Image (croppedImageUri.toString()))
440+ }
441+ }
442+ }
309443}
0 commit comments