diff --git a/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt b/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt index 0ca3894..3a3ad4a 100644 --- a/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt +++ b/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt @@ -10,6 +10,7 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build +import android.provider.MediaStore import android.util.Base64 import android.util.Log import androidx.core.content.FileProvider @@ -29,6 +30,7 @@ class MainActivity : FlutterActivity() { private val MAX_BRANDING_ICON_SIZE = 192 private val FILE_PICKER_REQUEST_CODE = 4201 private var pendingFilePickerResult: MethodChannel.Result? = null + private var pendingCameraCaptureUri: Uri? = null override fun onCreate(savedInstanceState: android.os.Bundle?) { // 华为/荣耀/OPPO 等国产设备:在任意网络请求之前同步安装 Conscrypt,修复 SSL 握手失败(无 GMS 时系统 SSL 实现不完整) @@ -116,7 +118,8 @@ class MainActivity : FlutterActivity() { "pickFiles" -> { val acceptTypes = call.argument>("acceptTypes") ?: emptyList() val allowMultiple = call.argument("allowMultiple") ?: false - openNativeFilePicker(acceptTypes, allowMultiple, result) + val capture = call.argument("capture") ?: false + openNativeFilePicker(acceptTypes, allowMultiple, capture, result) } else -> { result.notImplemented() @@ -134,7 +137,20 @@ class MainActivity : FlutterActivity() { return } - if (resultCode != Activity.RESULT_OK || data == null) { + if (resultCode != Activity.RESULT_OK) { + pendingCameraCaptureUri = null + result.success(emptyList()) + return + } + + pendingCameraCaptureUri?.let { uri -> + pendingCameraCaptureUri = null + grantPickedFileReadPermission(uri) + result.success(listOf(uri.toString())) + return + } + + if (data == null) { result.success(emptyList()) return } @@ -164,6 +180,7 @@ class MainActivity : FlutterActivity() { private fun openNativeFilePicker( acceptTypes: List, allowMultiple: Boolean, + capture: Boolean, result: MethodChannel.Result ) { if (pendingFilePickerResult != null) { @@ -172,6 +189,10 @@ class MainActivity : FlutterActivity() { } val mimeTypes = normalizeMimeTypes(acceptTypes) + if (capture && openNativeCapture(mimeTypes, result)) { + return + } + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = when (mimeTypes.size) { @@ -199,6 +220,50 @@ class MainActivity : FlutterActivity() { } } + private fun openNativeCapture( + mimeTypes: List, + result: MethodChannel.Result + ): Boolean { + pendingCameraCaptureUri = null + val captureVideo = mimeTypes.isNotEmpty() && + mimeTypes.all { it.startsWith("video/") } + val intent = if (captureVideo) { + Intent(MediaStore.ACTION_VIDEO_CAPTURE) + } else { + Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { + val outputUri = createCameraCaptureUri(".jpg") + pendingCameraCaptureUri = outputUri + putExtra(MediaStore.EXTRA_OUTPUT, outputUri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + } + + pendingFilePickerResult = result + return try { + startActivityForResult(intent, FILE_PICKER_REQUEST_CODE) + true + } catch (e: Exception) { + pendingFilePickerResult = null + pendingCameraCaptureUri = null + false + } + } + + private fun createCameraCaptureUri(extension: String): Uri { + val directory = externalCacheDir ?: cacheDir + val file = File.createTempFile( + "camera-capture-${System.currentTimeMillis()}", + extension, + directory + ) + return FileProvider.getUriForFile( + this, + "${packageName}.fileprovider", + file + ) + } + private fun normalizeMimeTypes(acceptTypes: List): List { val mimeTypes = linkedSetOf() acceptTypes diff --git a/lib/main.dart b/lib/main.dart index e4f9116..1a567aa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -204,6 +204,7 @@ class _H5ShellPageState extends State { unawaited( platformController.setOnShowFileSelector(_handleAndroidFileSelection), ); + unawaited(platformController.setMediaPlaybackRequiresUserGesture(false)); } } @@ -220,6 +221,7 @@ class _H5ShellPageState extends State { { 'acceptTypes': params.acceptTypes, 'allowMultiple': params.mode == FileSelectorMode.openMultiple, + 'capture': params.isCaptureEnabled, }, ); return result ?? [];