|
|
|
|
@ -14,6 +14,7 @@ import io.flutter.embedding.engine.FlutterEngine
|
|
|
|
|
import io.flutter.embedding.engine.FlutterEngineCache
|
|
|
|
|
import java.io.File
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Activity that handles ACTION_GET_CONTENT and ACTION_PICK intents
|
|
|
|
|
* Communicates with Flutter to get the selected image URI
|
|
|
|
|
@ -25,7 +26,7 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
Log.d(TAG, "onCreate() called")
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val action = intent.action
|
|
|
|
|
val type = intent.type
|
|
|
|
|
|
|
|
|
|
@ -36,7 +37,7 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
} else {
|
|
|
|
|
// Invalid intent, finish immediately
|
|
|
|
|
Log.w(TAG, "Invalid intent action or type, finishing activity")
|
|
|
|
|
setResult(Activity.RESULT_CANCELED)
|
|
|
|
|
setResult(RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
}
|
|
|
|
|
Log.d(TAG, "onCreate() finished")
|
|
|
|
|
@ -45,17 +46,17 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
|
|
|
|
Log.d(TAG, "configureFlutterEngine() called, hasRequestedImage = $hasRequestedImage")
|
|
|
|
|
super.configureFlutterEngine(flutterEngine)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Register all plugins
|
|
|
|
|
Log.d(TAG, "Registering plugins...")
|
|
|
|
|
MainActivity.registerPlugins(this, flutterEngine)
|
|
|
|
|
Log.d(TAG, "Plugins registered")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set up the image picker API
|
|
|
|
|
Log.d(TAG, "Setting up ImagePickerProviderApi...")
|
|
|
|
|
imagePickerApi = ImagePickerProviderApi(flutterEngine.dartExecutor.binaryMessenger)
|
|
|
|
|
Log.d(TAG, "ImagePickerProviderApi set up: ${imagePickerApi != null}")
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "ImagePickerProviderApi set up: ${true}")
|
|
|
|
|
|
|
|
|
|
// Check if this is a valid image picker intent and we haven't requested yet
|
|
|
|
|
val action = intent.action
|
|
|
|
|
if (!hasRequestedImage && (action == Intent.ACTION_GET_CONTENT || action == Intent.ACTION_PICK)) {
|
|
|
|
|
@ -63,7 +64,10 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
hasRequestedImage = true
|
|
|
|
|
requestImageFromFlutter()
|
|
|
|
|
} else {
|
|
|
|
|
Log.w(TAG, "NOT calling requestImageFromFlutter() - hasRequestedImage: $hasRequestedImage, action: $action")
|
|
|
|
|
Log.w(
|
|
|
|
|
TAG,
|
|
|
|
|
"NOT calling requestImageFromFlutter() - hasRequestedImage: $hasRequestedImage, action: $action"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
Log.d(TAG, "configureFlutterEngine() finished")
|
|
|
|
|
}
|
|
|
|
|
@ -71,82 +75,77 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
private fun requestImageFromFlutter() {
|
|
|
|
|
Log.d(TAG, "=== requestImageFromFlutter() CALLED ===")
|
|
|
|
|
Log.d(TAG, "imagePickerApi is null: ${imagePickerApi == null}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check if the calling app allows multiple selection
|
|
|
|
|
val allowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
|
|
|
|
|
Log.d(TAG, "Intent allows multiple selection: $allowMultiple")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
imagePickerApi?.pickImagesForIntent { result ->
|
|
|
|
|
Log.d(TAG, "pickImagesForIntent callback received")
|
|
|
|
|
result.fold(
|
|
|
|
|
onSuccess = { imageUriList ->
|
|
|
|
|
Log.d(TAG, "SUCCESS: Received ${imageUriList?.size ?: 0} image URI(s) from Flutter")
|
|
|
|
|
|
|
|
|
|
if (imageUriList.isNullOrEmpty()) {
|
|
|
|
|
// User cancelled or no images selected
|
|
|
|
|
Log.d(TAG, "No images selected, returning RESULT_CANCELED")
|
|
|
|
|
setResult(Activity.RESULT_CANCELED)
|
|
|
|
|
result.fold(onSuccess = { imageUriList ->
|
|
|
|
|
Log.d(TAG, "SUCCESS: Received ${imageUriList?.size ?: 0} image URI(s) from Flutter")
|
|
|
|
|
|
|
|
|
|
if (imageUriList.isNullOrEmpty()) {
|
|
|
|
|
// User cancelled or no images selected
|
|
|
|
|
Log.d(TAG, "No images selected, returning RESULT_CANCELED")
|
|
|
|
|
setResult(RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
return@fold
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Convert all URIs to content URIs
|
|
|
|
|
val contentUris = imageUriList.filterNotNull().map { uriString ->
|
|
|
|
|
try {
|
|
|
|
|
convertToContentUri(uriString)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Log.e(TAG, "Error converting URI: $uriString", e)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (contentUris.isEmpty()) {
|
|
|
|
|
Log.e(TAG, "No valid content URIs after conversion")
|
|
|
|
|
setResult(RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
return@fold
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Convert all URIs to content URIs
|
|
|
|
|
val contentUris = imageUriList.mapNotNull { uriString ->
|
|
|
|
|
uriString?.let {
|
|
|
|
|
try {
|
|
|
|
|
convertToContentUri(it)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Log.e(TAG, "Error converting URI: $it", e)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (contentUris.isEmpty()) {
|
|
|
|
|
Log.e(TAG, "No valid content URIs after conversion")
|
|
|
|
|
setResult(Activity.RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
return@fold
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val resultIntent = Intent()
|
|
|
|
|
|
|
|
|
|
if (contentUris.size == 1 || !allowMultiple) {
|
|
|
|
|
// Single image or app doesn't support multiple
|
|
|
|
|
Log.d(TAG, "Returning single image URI: ${contentUris.first()}")
|
|
|
|
|
resultIntent.data = contentUris.first()
|
|
|
|
|
} else {
|
|
|
|
|
// Multiple images - use ClipData
|
|
|
|
|
Log.d(TAG, "Returning ${contentUris.size} images using ClipData")
|
|
|
|
|
val clipData = ClipData.newUri(contentResolver, "Images", contentUris.first())
|
|
|
|
|
|
|
|
|
|
// Add the rest of the URIs to ClipData
|
|
|
|
|
for (i in 1 until contentUris.size) {
|
|
|
|
|
clipData.addItem(ClipData.Item(contentUris[i]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resultIntent.clipData = clipData
|
|
|
|
|
resultIntent.data = contentUris.first() // Also set primary URI for compatibility
|
|
|
|
|
|
|
|
|
|
val resultIntent = Intent()
|
|
|
|
|
|
|
|
|
|
if (contentUris.size == 1 || !allowMultiple) {
|
|
|
|
|
// Single image or app doesn't support multiple
|
|
|
|
|
Log.d(TAG, "Returning single image URI: ${contentUris.first()}")
|
|
|
|
|
resultIntent.data = contentUris.first()
|
|
|
|
|
} else {
|
|
|
|
|
// Multiple images - use ClipData
|
|
|
|
|
Log.d(TAG, "Returning ${contentUris.size} images using ClipData")
|
|
|
|
|
val clipData = ClipData.newUri(contentResolver, "Images", contentUris.first())
|
|
|
|
|
|
|
|
|
|
// Add the rest of the URIs to ClipData
|
|
|
|
|
for (i in 1 until contentUris.size) {
|
|
|
|
|
clipData.addItem(ClipData.Item(contentUris[i]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Grant temporary read permission to all URIs
|
|
|
|
|
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
|
|
|
|
|
|
|
|
setResult(Activity.RESULT_OK, resultIntent)
|
|
|
|
|
finish()
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Log.e(TAG, "Error processing URIs", e)
|
|
|
|
|
setResult(Activity.RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
resultIntent.clipData = clipData
|
|
|
|
|
resultIntent.data = contentUris.first() // Also set primary URI for compatibility
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onFailure = { error ->
|
|
|
|
|
Log.e(TAG, "Error getting images from Flutter", error)
|
|
|
|
|
setResult(Activity.RESULT_CANCELED)
|
|
|
|
|
|
|
|
|
|
// Grant temporary read permission to all URIs
|
|
|
|
|
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
|
|
|
|
|
|
|
|
setResult(RESULT_OK, resultIntent)
|
|
|
|
|
finish()
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Log.e(TAG, "Error processing URIs", e)
|
|
|
|
|
setResult(RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}, onFailure = { error ->
|
|
|
|
|
Log.e(TAG, "Error getting images from Flutter", error)
|
|
|
|
|
setResult(RESULT_CANCELED)
|
|
|
|
|
finish()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -156,13 +155,11 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
*/
|
|
|
|
|
private fun convertToContentUri(uriString: String): Uri {
|
|
|
|
|
val uri = uriString.toUri()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return if (uri.scheme == "file") {
|
|
|
|
|
val file = File(uri.path!!)
|
|
|
|
|
FileProvider.getUriForFile(
|
|
|
|
|
this,
|
|
|
|
|
"${applicationContext.packageName}.fileprovider",
|
|
|
|
|
file
|
|
|
|
|
this, "${applicationContext.packageName}.fileprovider", file
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// Already a content URI or other type
|
|
|
|
|
@ -172,7 +169,7 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
|
|
|
|
|
override fun getCachedEngineId(): String? {
|
|
|
|
|
// Try to use the cached engine if available
|
|
|
|
|
val hasCachedEngine = FlutterEngineCache.getInstance().contains("immich_engine")
|
|
|
|
|
val hasCachedEngine = FlutterEngineCache.getInstance().contains(ENGINE_CACHE_KEY)
|
|
|
|
|
Log.d(TAG, "getCachedEngineId() called, has cached engine: $hasCachedEngine")
|
|
|
|
|
return if (hasCachedEngine) {
|
|
|
|
|
Log.d(TAG, "Using cached engine 'immich_engine'")
|
|
|
|
|
@ -182,22 +179,22 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onStart() {
|
|
|
|
|
super.onStart()
|
|
|
|
|
Log.d(TAG, "onStart() called")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
|
super.onResume()
|
|
|
|
|
Log.d(TAG, "onResume() called")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onPause() {
|
|
|
|
|
super.onPause()
|
|
|
|
|
Log.d(TAG, "onPause() called")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
|
Log.d(TAG, "onDestroy() called")
|
|
|
|
|
super.onDestroy()
|
|
|
|
|
@ -205,5 +202,7 @@ class ImagePickerActivity : FlutterActivity() {
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private const val TAG = "ImagePickerActivity"
|
|
|
|
|
const val ENGINE_CACHE_KEY = "immich::image_picker::engine"
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|