SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/util/VisualMediaPickerCompat.kt

217 lines
7.5 KiB
Kotlin

package jp.juggler.subwaytooter.util
import android.Manifest
import android.content.Intent
import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.pref.PrefDevice
import jp.juggler.subwaytooter.pref.prefDevice
import jp.juggler.util.coroutine.launchAndShowError
import jp.juggler.util.data.UriAndType
import jp.juggler.util.data.checkMimeTypeAndGrant
import jp.juggler.util.data.intentGetContent
import jp.juggler.util.data.notEmpty
import jp.juggler.util.log.LogCategory
import jp.juggler.util.log.showError
import jp.juggler.util.ui.isOk
class VisualMediaPickerCompat(
private val onPicked: suspend (entries: List<UriAndType>?) -> Unit,
) {
companion object {
private val log = LogCategory("VisualMediaPickerCompat")
}
private var activity: AppCompatActivity? = null
private var prefDevice: PrefDevice? = null
private var pickMedia1: ActivityResultLauncher<PickVisualMediaRequest>? = null
private var pickMediaMultiple: ActivityResultLauncher<PickVisualMediaRequest>? = null
private var arSafPicker: ActivityResultLauncher<Intent>? = null
/**
* SAFのピッカーを使うか判定するのに使う
*/
private val prSafPickerImage = when {
// 34以降でも権限をチェック
Build.VERSION.SDK_INT >= 33 -> PermissionSpec(
permissions = listOf(
Manifest.permission.READ_MEDIA_IMAGES,
),
deniedId = R.string.permission_denied_media_access,
rationalId = R.string.permission_rational_media_access,
)
else -> PermissionSpec(
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
deniedId = R.string.permission_denied_media_access,
rationalId = R.string.permission_rational_media_access,
)
}.requester {
openSafPicker(
multiple = prefDevice?.mediaPickerMultiple ?: false,
allowVideo = false,
)
}
/**
* SAFのピッカーを使うか判定するのに使う
*/
private val prSafPickerImageAndVideo = when {
// 34以降でも権限をチェック
Build.VERSION.SDK_INT >= 33 -> PermissionSpec(
permissions = listOf(
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
),
deniedId = R.string.permission_denied_media_access,
rationalId = R.string.permission_rational_media_access,
)
else -> PermissionSpec(
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
deniedId = R.string.permission_denied_media_access,
rationalId = R.string.permission_rational_media_access,
)
}.requester {
openSafPicker(
multiple = prefDevice?.mediaPickerMultiple ?: false,
allowVideo = true,
)
}
fun register(activity: AppCompatActivity, multipleLimit: Int = 4) {
prefDevice = activity.prefDevice
this.activity = activity
pickMedia1 = activity.registerForActivityResult(
ActivityResultContracts.PickVisualMedia(),
) { uri ->
activity.launchAndShowError {
onPicked((uri?.let { listOf(it) })?.checkMimeTypeAndGrant(activity.contentResolver))
}
}
pickMediaMultiple = activity.registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(multipleLimit),
) { uris ->
activity.launchAndShowError {
onPicked(uris?.notEmpty()?.checkMimeTypeAndGrant(activity.contentResolver))
}
}
prSafPickerImage.register(activity)
prSafPickerImageAndVideo.register(activity)
arSafPicker = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
) { r ->
activity.launchAndShowError {
if (r.isOk) {
onPicked(
r.data?.checkMimeTypeAndGrant(activity.contentResolver)
)
}
}
}
}
fun open(
multiple: Boolean = false,
allowVideo: Boolean = false,
) {
// SAFのピッカーに必要な権限を持っているか?
val hasSafPermissions = try {
when {
allowVideo -> prSafPickerImageAndVideo
else -> prSafPickerImage
}.hasPermissions()
} catch (ex: Throwable) {
log.e(ex, "can't check media permissions.")
activity?.showError(ex, "can't check media permissions.")
return
}
when {
// API 33まで、またはAPI34以降でも権限があればSAFのピッカーを使う
Build.VERSION.SDK_INT < 34 || hasSafPermissions -> openSafPicker(
multiple = multiple,
allowVideo = allowVideo,
)
// API34以降で権限が不十分ならJetPack Activity の写真選択ツールを使う
else -> openVisualMediaPicker(
multiple = multiple,
allowVideo = allowVideo,
)
}
}
private fun openVisualMediaPicker(
multiple: Boolean,
allowVideo: Boolean,
) {
log.i("openVisualMediaPicker multiple=$multiple, allowVideo=$allowVideo")
val mediaType = when (allowVideo) {
true -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
else -> ActivityResultContracts.PickVisualMedia.ImageOnly
}
val pickerLauncher = when {
multiple -> pickMediaMultiple
else -> pickMedia1
}
pickerLauncher ?: error("openVisualMediaPicker: pickerLauncher is not registered.")
pickerLauncher.launch(PickVisualMediaRequest(mediaType))
}
private fun openSafPicker(
multiple: Boolean,
allowVideo: Boolean,
) {
log.i("openSafPicker multiple=$multiple, allowVideo=$allowVideo")
val activity = this.activity
?: error("missing activity")
prefDevice?.mediaPickerMultiple = multiple
val permissionRequester = when {
allowVideo -> prSafPickerImageAndVideo
else -> prSafPickerImage
}
if (!permissionRequester.checkOrLaunch()) return
// SAFのIntentで開く
try {
val captionId = when {
multiple -> when {
allowVideo -> R.string.pick_images_or_video
else -> R.string.pick_images
}
else -> R.string.pick_image
}
val intent = intentGetContent(
allowMultiple = true,
caption = activity.getString(captionId),
mimeTypes = when {
allowVideo -> arrayOf("image/*", "video/*")
else -> arrayOf("image/*")
}
)
arSafPicker!!.launch(intent)
} catch (ex: Throwable) {
activity.showError(ex, "openVisualMediaPicker33 failed.")
}
}
}