217 lines
7.5 KiB
Kotlin
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.")
|
||
|
}
|
||
|
}
|
||
|
}
|