Skip to content

Commit

Permalink
icerockdev#34 add MediaPickerDelegate
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Nesterov committed May 2, 2024
1 parent d358b7f commit 39c2649
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@
package dev.icerock.moko.media.picker

import android.app.Activity
import android.content.Context
import android.net.Uri
import android.os.Environment
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import dev.icerock.moko.media.Bitmap
import dev.icerock.moko.media.BitmapUtils
import dev.icerock.moko.media.FileMedia
import dev.icerock.moko.media.Media
import dev.icerock.moko.permissions.Permission
Expand All @@ -44,37 +41,20 @@ internal class MediaPickerControllerImpl(

private val codeCallbackMap = mutableMapOf<Int, CallbackData<*>>()

private val pickVisualMediaLauncherHolder = MutableStateFlow<ActivityResultLauncher<PickVisualMediaRequest>?>(null)
private val pickFileMediaLauncherHolder = MutableStateFlow<ActivityResultLauncher<Array<String>>?>(null)

private val imagePickerDelegate = ImagePickerDelegate()

private val maxImageWidth
get() = DEFAULT_MAX_IMAGE_WIDTH
private val maxImageHeight
get() = DEFAULT_MAX_IMAGE_HEIGHT
private val mediaPickerDelegate = MediaPickerDelegate()

override fun bind(activity: ComponentActivity) {
this.activityHolder.value = activity
permissionsController.bind(activity)

imagePickerDelegate.bind(activity)
mediaPickerDelegate.bind(activity)

val activityResultRegistryOwner = activity as ActivityResultRegistryOwner

val pickVisualMediaLauncher = activityResultRegistryOwner.activityResultRegistry.register(
"PickVisualMedia-$key",
ActivityResultContracts.PickVisualMedia()
) { uri ->
val callbackData = codeCallbackMap.values.last() as CallbackData<android.graphics.Bitmap>
val callback = callbackData.callback
if (uri != null) {
processResult(activity, callback, uri)
} else {
callback.invoke(Result.failure(CanceledException()))
}
}

val pickFileMediaLauncher = activityResultRegistryOwner.activityResultRegistry.register(
"PickFileMedia-$key",
ActivityResultContracts.OpenDocument()
Expand All @@ -96,15 +76,13 @@ internal class MediaPickerControllerImpl(
}
}

pickVisualMediaLauncherHolder.value = pickVisualMediaLauncher
pickFileMediaLauncherHolder.value = pickFileMediaLauncher

val observer = object : LifecycleObserver {

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroyed(source: LifecycleOwner) {
this@MediaPickerControllerImpl.activityHolder.value = null
this@MediaPickerControllerImpl.pickVisualMediaLauncherHolder.value = null
this@MediaPickerControllerImpl.pickFileMediaLauncherHolder.value = null
source.lifecycle.removeObserver(this)
}
Expand Down Expand Up @@ -149,16 +127,6 @@ internal class MediaPickerControllerImpl(
return Bitmap(bitmap)
}

private fun pickMediaFile(callback: (Result<Media>) -> Unit) {
val requestCode = codeCallbackMap.keys.sorted().lastOrNull() ?: 0
codeCallbackMap[requestCode] =
CallbackData.Media(
callback
)
val launcher = pickVisualMediaLauncherHolder.value
launcher?.launch(PickVisualMediaRequest())
}

private suspend fun createPhotoUri(): Uri {
val context = awaitActivity()
val filesDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
Expand All @@ -177,7 +145,7 @@ internal class MediaPickerControllerImpl(

return suspendCoroutine { continuation ->
val action: (Result<Media>) -> Unit = { continuation.resumeWith(it) }
pickMediaFile(action)
mediaPickerDelegate.pickMedia(action)
}
}

Expand All @@ -195,41 +163,6 @@ internal class MediaPickerControllerImpl(
)
}

@Suppress("ReturnCount")
private fun processResult(
context: Context,
callback: (Result<android.graphics.Bitmap>) -> Unit,
uri: Uri
) {
val contentResolver = context.contentResolver

val bitmapOptions = contentResolver.openInputStream(uri)?.use {
BitmapUtils.getBitmapOptionsFromStream(it)
} ?: run {
callback.invoke(Result.failure(NoAccessToFileException(uri.toString())))
return
}

val sampleSize =
BitmapUtils.calculateInSampleSize(bitmapOptions, maxImageWidth, maxImageHeight)

val orientation = contentResolver.openInputStream(uri)?.use {
BitmapUtils.getBitmapOrientation(it)
} ?: run {
callback.invoke(Result.failure(NoAccessToFileException(uri.toString())))
return
}

val bitmap = contentResolver.openInputStream(uri)?.use {
BitmapUtils.getNormalizedBitmap(it, orientation, sampleSize)
} ?: run {
callback.invoke(Result.failure(NoAccessToFileException(uri.toString())))
return
}

callback.invoke(Result.success(bitmap))
}

override suspend fun pickFiles(): FileMedia {
permissionsController.providePermission(Permission.STORAGE)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package dev.icerock.moko.media.picker

import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import dev.icerock.moko.media.Media
import dev.icerock.moko.media.MediaFactory
import kotlinx.coroutines.flow.MutableStateFlow

internal class MediaPickerDelegate {

private var callback: CallbackData? = null

private val mediaPickerLauncherHolder =
MutableStateFlow<ActivityResultLauncher<PickVisualMediaRequest>?>(null)

fun bind(activity: ComponentActivity) {
val activityResultRegistryOwner = activity as ActivityResultRegistryOwner
val activityResultRegistry = activityResultRegistryOwner.activityResultRegistry

mediaPickerLauncherHolder.value = activityResultRegistry.register(
PICK_MEDIA_KEY,
ActivityResultContracts.PickVisualMedia()
) { uri ->
val callbackData = callback ?: return@register
callback = null

val callback = callbackData.callback

if (uri == null) {
callback.invoke(Result.failure(CanceledException()))
return@register
}

val result = kotlin.runCatching {
MediaFactory.create(activity, uri)
}
callback.invoke(result)
}

val observer = object : LifecycleEventObserver {

override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
mediaPickerLauncherHolder.value = null
source.lifecycle.removeObserver(this)
}
}
}
activity.lifecycle.addObserver(observer)
}

fun pickMedia(callback: (Result<Media>) -> Unit) {
this.callback?.let {
it.callback.invoke(Result.failure(IllegalStateException("Callback should be null")))
this.callback = null
}
this.callback = CallbackData(callback)

mediaPickerLauncherHolder.value?.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)
)
}

class CallbackData(val callback: (Result<Media>) -> Unit)

companion object {
private const val PICK_MEDIA_KEY = "PickMediaKey"
}
}

0 comments on commit 39c2649

Please sign in to comment.