fix(tv-picker): native Android TV image picker for branded/slideshow + bump 1.0.10+11
This commit is contained in:
@@ -1,5 +1,217 @@
|
||||
package com.jamshalat.jamshalat_masjid_screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import android.provider.OpenableColumns
|
||||
import android.webkit.MimeTypeMap
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
class MainActivity : FlutterActivity() {
|
||||
private companion object {
|
||||
const val CHANNEL_NAME = "jamshalat/tv_media_picker"
|
||||
const val METHOD_PICK_IMAGES = "pickImages"
|
||||
const val METHOD_LIST_HANDLERS = "listPickers"
|
||||
const val REQUEST_PICK_IMAGES = 49011
|
||||
}
|
||||
|
||||
private var pendingResult: MethodChannel.Result? = null
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
METHOD_PICK_IMAGES -> handlePickImages(call, result)
|
||||
METHOD_LIST_HANDLERS -> result.success(listPickers())
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePickImages(call: MethodCall, result: MethodChannel.Result) {
|
||||
if (pendingResult != null) {
|
||||
result.error("BUSY", "Media picker request is already running.", null)
|
||||
return
|
||||
}
|
||||
|
||||
val allowMultiple = call.argument<Boolean>("allowMultiple") ?: false
|
||||
val intent = createPickIntent(allowMultiple)
|
||||
if (intent == null) {
|
||||
result.error(
|
||||
"NO_PICKER",
|
||||
"No compatible file manager/document picker found.",
|
||||
listPickers(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
pendingResult = result
|
||||
startActivityForResult(intent, REQUEST_PICK_IMAGES)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode != REQUEST_PICK_IMAGES) return
|
||||
|
||||
val result = pendingResult ?: return
|
||||
pendingResult = null
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
result.success(emptyList<String>())
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val uris = mutableListOf<Uri>()
|
||||
data?.data?.let { uris.add(it) }
|
||||
data?.clipData?.let { clip ->
|
||||
for (index in 0 until clip.itemCount) {
|
||||
clip.getItemAt(index).uri?.let { uris.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val uniqueUris = uris.distinctBy { it.toString() }
|
||||
if (uniqueUris.isEmpty()) {
|
||||
result.success(emptyList<String>())
|
||||
return
|
||||
}
|
||||
|
||||
val copiedPaths = mutableListOf<String>()
|
||||
for (uri in uniqueUris) {
|
||||
copyUriToCache(uri)?.let { copiedPaths.add(it) }
|
||||
}
|
||||
result.success(copiedPaths)
|
||||
} catch (error: Exception) {
|
||||
result.error("PICK_FAILED", error.message, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPickIntent(allowMultiple: Boolean): Intent? {
|
||||
val openDocumentIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "image/*"
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
|
||||
putExtra(Intent.EXTRA_LOCAL_ONLY, true)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
putExtra(
|
||||
DocumentsContract.EXTRA_INITIAL_URI,
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (openDocumentIntent.resolveActivity(packageManager) != null) {
|
||||
return openDocumentIntent
|
||||
}
|
||||
|
||||
val getContentIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "image/*"
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
|
||||
}
|
||||
if (getContentIntent.resolveActivity(packageManager) != null) {
|
||||
return Intent.createChooser(getContentIntent, "Pilih gambar")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun listPickers(): List<Map<String, String>> {
|
||||
val result = LinkedHashMap<String, Map<String, String>>()
|
||||
val intents = listOf(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "image/*"
|
||||
},
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "image/*"
|
||||
},
|
||||
)
|
||||
|
||||
for (intent in intents) {
|
||||
val resolved = packageManager.queryIntentActivities(intent, 0)
|
||||
for (info in resolved) {
|
||||
val packageName = info.activityInfo.packageName ?: continue
|
||||
if (result.containsKey(packageName)) continue
|
||||
val label = info.loadLabel(packageManager)?.toString()?.trim().orEmpty()
|
||||
result[packageName] = mapOf(
|
||||
"packageName" to packageName,
|
||||
"label" to if (label.isEmpty()) packageName else label,
|
||||
)
|
||||
}
|
||||
}
|
||||
return result.values.toList()
|
||||
}
|
||||
|
||||
private fun copyUriToCache(uri: Uri): String? {
|
||||
if (uri.scheme == "content") {
|
||||
try {
|
||||
contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||
)
|
||||
} catch (_: SecurityException) {
|
||||
// Non-persistable provider, ignore.
|
||||
} catch (_: UnsupportedOperationException) {
|
||||
// Provider doesn't support persisted grants.
|
||||
}
|
||||
}
|
||||
|
||||
val inputStream = contentResolver.openInputStream(uri) ?: return null
|
||||
inputStream.use { input ->
|
||||
val ext = resolveExtension(uri)
|
||||
val folder = File(cacheDir, "picked_images")
|
||||
if (!folder.exists()) {
|
||||
folder.mkdirs()
|
||||
}
|
||||
|
||||
val fileName = "img_${System.currentTimeMillis()}_${Random.nextInt(1000, 9999)}.$ext"
|
||||
val outputFile = File(folder, fileName)
|
||||
FileOutputStream(outputFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
return outputFile.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveExtension(uri: Uri): String {
|
||||
val mimeType = contentResolver.getType(uri)
|
||||
val extFromMime = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
||||
if (!extFromMime.isNullOrBlank()) {
|
||||
return extFromMime
|
||||
}
|
||||
|
||||
val displayName = queryDisplayName(uri)
|
||||
if (!displayName.isNullOrBlank()) {
|
||||
val dot = displayName.lastIndexOf('.')
|
||||
if (dot in 1 until displayName.length - 1) {
|
||||
return displayName.substring(dot + 1)
|
||||
}
|
||||
}
|
||||
return "jpg"
|
||||
}
|
||||
|
||||
private fun queryDisplayName(uri: Uri): String? {
|
||||
contentResolver.query(
|
||||
uri,
|
||||
arrayOf(OpenableColumns.DISPLAY_NAME),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(0)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user