From 42155d9e31bf103dc26a668b51e7dc9d4639232d Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 9 Sep 2023 00:30:56 +0200 Subject: [PATCH] final commit --- example/lib/main.dart | 30 ++- .../moxplatform_android/android/build.gradle | 1 + .../me/polynom/moxplatform_android/Api.java | 45 ++++ .../MoxplatformAndroidPlugin.java | 40 +++- .../me/polynom/moxplatform_android/Picker.kt | 226 ++++++++++++++++++ .../lib/src/platform_android.dart | 6 + .../lib/src/api.g.dart | 137 ++++++----- .../lib/src/platform.dart | 4 + .../lib/src/platform_stub.dart | 4 + pigeons/api.dart | 11 + 10 files changed, 432 insertions(+), 72 deletions(-) create mode 100644 packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Picker.kt diff --git a/example/lib/main.dart b/example/lib/main.dart index 20dc0b2..57b9c69 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -202,7 +202,35 @@ class MyHomePageState extends State { ); }, child: const Text('Thumbnail'), - ) + ), + ElevatedButton( + onPressed: () async { + final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.image, false); + print('Picked files $result'); + }, + child: const Text('Pick image'), + ), + ElevatedButton( + onPressed: () async { + final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.image, true); + print('Picked files $result'); + }, + child: const Text('Pick multiple images'), + ), + ElevatedButton( + onPressed: () async { + final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.imageAndVideo, true); + print('Picked files $result'); + }, + child: const Text('Pick multiple images and videos'), + ), + ElevatedButton( + onPressed: () async { + final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.generic, true); + print('Picked files $result'); + }, + child: const Text('Pick multiple generic files'), + ), ], ), ), diff --git a/packages/moxplatform_android/android/build.gradle b/packages/moxplatform_android/android/build.gradle index 64bb097..c2786e0 100644 --- a/packages/moxplatform_android/android/build.gradle +++ b/packages/moxplatform_android/android/build.gradle @@ -44,4 +44,5 @@ android { dependencies { implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.core:core:1.10.1' + implementation 'androidx.activity:activity:1.7.2' } \ No newline at end of file diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java index c8b1cc4..b3e45a7 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java @@ -81,6 +81,19 @@ public class Api { } } + public enum FilePickerType { + IMAGE(0), + VIDEO(1), + IMAGE_AND_VIDEO(2), + GENERIC(3); + + final int index; + + private FilePickerType(final int index) { + this.index = index; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class CryptographyResult { private @NonNull byte[] plaintextHash; @@ -211,6 +224,8 @@ public class Api { /** Media APIs */ @NonNull Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth); + /** Picker */ + void pickFiles(@NonNull FilePickerType type, @NonNull Boolean pickMultiple, @NonNull Result> result); /** The codec used by MoxplatformApi. */ static @NonNull MessageCodec getCodec() { @@ -457,6 +472,36 @@ public class Api { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.pickFiles", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + FilePickerType typeArg = args.get(0) == null ? null : FilePickerType.values()[(int) args.get(0)]; + Boolean pickMultipleArg = (Boolean) args.get(1); + Result> resultCallback = + new Result>() { + public void success(List result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.pickFiles(typeArg, pickMultipleArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } } diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java index 64a55d2..7cc6db4 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java @@ -6,6 +6,8 @@ import static androidx.core.content.ContextCompat.startActivity; import static me.polynom.moxplatform_android.ConstantsKt.MOXPLATFORM_FILEPROVIDER_ID; import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY; import static me.polynom.moxplatform_android.CryptoKt.*; +import static me.polynom.moxplatform_android.PickerKt.filePickerRequest; +import static me.polynom.moxplatform_android.PickerKt.onActivityResultImpl; import static me.polynom.moxplatform_android.RecordSentMessageKt.*; import static me.polynom.moxplatform_android.ThumbnailsKt.generateVideoThumbnailImplementation; @@ -31,6 +33,8 @@ import android.provider.MediaStore; import android.util.Log; import android.util.Size; +import androidx.activity.result.PickVisualMediaRequest; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; @@ -42,7 +46,11 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.service.ServiceAware; @@ -55,11 +63,14 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import kotlin.Unit; import kotlin.jvm.functions.Function1; -public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, MoxplatformApi { +public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, ActivityAware, PluginRegistry.ActivityResultListener, MoxplatformApi { public static final String entrypointKey = "entrypoint_handle"; public static final String extraDataKey = "extra_data"; private static final String autoStartAtBootKey = "auto_start_at_boot"; @@ -72,13 +83,17 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt private MethodChannel channel; public static Activity activity; - private Context context; public MoxplatformAndroidPlugin() { _instances.add(this); } + @Override + public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + return onActivityResultImpl(context, requestCode, resultCode, data); + } + @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey); @@ -279,4 +294,25 @@ public class MoxplatformAndroidPlugin extends BroadcastReceiver implements Flutt public Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth) { return generateVideoThumbnailImplementation(src, dest, maxWidth); } + + @Override + public void pickFiles(@NonNull FilePickerType type, @NonNull Boolean pickMultiple, @NonNull Api.Result> result) { + filePickerRequest(context, activity, type, pickMultiple, result); + } + + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + activity = binding.getActivity(); + binding.addActivityResultListener(this); + Log.d(TAG, "Activity attached"); + } + + @Override + public void onDetachedFromActivity() {} + + @Override + public void onDetachedFromActivityForConfigChanges() {} + + @Override + public void onReattachedToActivityForConfigChanges​(ActivityPluginBinding binding) {} } diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Picker.kt b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Picker.kt new file mode 100644 index 0000000..6431fd5 --- /dev/null +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Picker.kt @@ -0,0 +1,226 @@ +package me.polynom.moxplatform_android + +import android.app.Activity +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.net.Uri +import android.provider.MediaStore +import android.util.Log +import android.webkit.MimeTypeMap +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.util.UUID + + +object RequestTracker { + val requests: MutableMap> = mutableMapOf() +} + +const val PICK_FILE_REQUEST = 41; +const val PICK_FILES_REQUEST = 42; + +fun genericFilePickerRequest(activity: Activity?, pickMultiple: Boolean, result: Api.Result>) { + val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/*" + + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, pickMultiple); + } + + RequestTracker.requests[PICK_FILE_REQUEST] = result as Api.Result; + activity?.startActivityForResult(pickIntent, PICK_FILE_REQUEST) +} + +fun filePickerRequest( + context: Context, + activity: Activity?, + type: Api.FilePickerType, + pickMultiple: Boolean, + result: Api.Result> +) { + if (type == Api.FilePickerType.GENERIC) { + return genericFilePickerRequest(activity, pickMultiple, result) + } + + val pickerType = when (type) { + Api.FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly + Api.FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly + Api.FilePickerType.IMAGE_AND_VIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo + // TODO + Api.FilePickerType.GENERIC -> ActivityResultContracts.PickVisualMedia.ImageAndVideo + } + + val pick = when (pickMultiple) { + false -> ActivityResultContracts.PickVisualMedia() + true -> ActivityResultContracts.PickMultipleVisualMedia() + } + + val requestCode = if (pickMultiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST + val pickIntent = pick.createIntent(context, PickVisualMediaRequest(pickerType)) + RequestTracker.requests[requestCode] = result as Api.Result + Log.d(TAG, "Tracked size ${RequestTracker.requests.size}") + + if (activity == null) { + Log.w(TAG, "Activity is null") + } + activity?.startActivityForResult(pickIntent, requestCode); +} + +/** + * Copies the file from the given content URI to a temporary directory, retaining the original + * file name if possible. + * + * + * Each file is placed in its own directory to avoid conflicts according to the following + * scheme: {cacheDir}/{randomUuid}/{fileName} + * + * + * File extension is changed to match MIME type of the file, if known. Otherwise, the extension + * is left unchanged. + * + * + * If the original file name is unknown, a predefined "image_picker" filename is used and the + * file extension is deduced from the mime type (with fallback to ".jpg" in case of failure). + */ +fun getPathFromUri(context: Context, uri: Uri): String? { + try { + context.contentResolver.openInputStream(uri).use { inputStream -> + val uuid = UUID.randomUUID().toString() + val targetDirectory = File(context.cacheDir, uuid) + targetDirectory.mkdir() + // TODO(SynSzakala) according to the docs, `deleteOnExit` does not work reliably on Android; we should preferably + // just clear the picked files after the app startup. + targetDirectory.deleteOnExit() + var fileName = getImageName(context, uri) + var extension = getImageExtension(context, uri) + if (fileName == null) { + Log.w("FileUtils", "Cannot get file name for $uri") + if (extension == null) extension = ".jpg" + fileName = "image_picker$extension" + } else if (extension != null) { + fileName = getBaseName(fileName) + extension + } + val file = File(targetDirectory, fileName) + FileOutputStream(file).use { outputStream -> + copy(inputStream!!, outputStream) + return file.path + } + } + } catch (e: IOException) { + // If closing the output stream fails, we cannot be sure that the + // target file was written in full. Flushing the stream merely moves + // the bytes into the OS, not necessarily to the file. + return null + } catch (e: SecurityException) { + // Calling `ContentResolver#openInputStream()` has been reported to throw a + // `SecurityException` on some devices in certain circumstances. Instead of crashing, we + // return `null`. + // + // See https://github.com/flutter/flutter/issues/100025 for more details. + return null + } +} + +/** @return extension of image with dot, or null if it's empty. + */ +private fun getImageExtension(context: Context, uriImage: Uri): String? { + val extension: String? + extension = try { + if (uriImage.scheme == ContentResolver.SCHEME_CONTENT) { + val mime = MimeTypeMap.getSingleton() + mime.getExtensionFromMimeType(context.contentResolver.getType(uriImage)) + } else { + MimeTypeMap.getFileExtensionFromUrl( + Uri.fromFile(File(uriImage.path)).toString() + ) + } + } catch (e: Exception) { + return null + } + return if (extension == null || extension.isEmpty()) { + null + } else ".$extension" +} + +/** @return name of the image provided by ContentResolver; this may be null. + */ +private fun getImageName(context: Context, uriImage: Uri): String? { + queryImageName(context, uriImage).use { cursor -> + return if (cursor == null || !cursor.moveToFirst() || (cursor.columnCount < 1)) null else cursor.getString( + 0 + ) + } +} + +private fun queryImageName(context: Context, uriImage: Uri): Cursor? { + return context + .contentResolver + .query(uriImage, arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null) +} + +@Throws(IOException::class) +private fun copy(`in`: InputStream, out: OutputStream) { + val buffer = ByteArray(4 * 1024) + var bytesRead: Int + while (`in`.read(buffer).also { bytesRead = it } != -1) { + out.write(buffer, 0, bytesRead) + } + out.flush() +} + +private fun getBaseName(fileName: String): String { + val lastDotIndex = fileName.lastIndexOf('.') + return if (lastDotIndex < 0) { + fileName + } else fileName.substring(0, lastDotIndex) + // Basename is everything before the last '.'. +} + +fun onActivityResultImpl(context: Context, requestCode: Int, resultCode: Int, data: Intent?): Boolean { + Log.d(TAG, "Got result for $requestCode with result $resultCode (${data?.action})") + if (requestCode == PICK_FILE_REQUEST || requestCode == PICK_FILES_REQUEST) { + Log.d(TAG, "Extra data ${data?.data}") + val result = RequestTracker.requests.remove(requestCode); + if (result == null) { + Log.w(TAG, "Untracked response.") + return false; + } + + if (resultCode != Activity.RESULT_OK) { + // No files picked + result!!.success(listOf()) + return true; + } + + val pickedMultiple = requestCode == PICK_FILES_REQUEST + val pickedFiles = mutableListOf() + if (pickedMultiple) { + val intentUris = data!!.clipData + if (data!!.clipData != null) { + for (i in 0 until data!!.clipData!!.itemCount) { + val path = getPathFromUri(context, data!!.clipData!!.getItemAt(i).uri) + if (path != null) { + pickedFiles.add(path ) + } + } + } + } else { + val path = getPathFromUri(context, data!!.data!!) + if (path != null) { + pickedFiles.add(path ) + } + } + + result!!.success(pickedFiles) + return true; + } + + return false; +} \ No newline at end of file diff --git a/packages/moxplatform_android/lib/src/platform_android.dart b/packages/moxplatform_android/lib/src/platform_android.dart index e3c0999..bc5db25 100644 --- a/packages/moxplatform_android/lib/src/platform_android.dart +++ b/packages/moxplatform_android/lib/src/platform_android.dart @@ -30,4 +30,10 @@ class AndroidPlatformImplementation extends PlatformImplementation { ) async { return MoxplatformInterface.api.generateVideoThumbnail(src, dest, width); } + + @override + Future> pickFiles(FilePickerType type, bool pickMultiple) async { + final result = await MoxplatformInterface.api.pickFiles(type, pickMultiple); + return result.cast(); + } } diff --git a/packages/moxplatform_platform_interface/lib/src/api.g.dart b/packages/moxplatform_platform_interface/lib/src/api.g.dart index 64c8dd7..a399d20 100644 --- a/packages/moxplatform_platform_interface/lib/src/api.g.dart +++ b/packages/moxplatform_platform_interface/lib/src/api.g.dart @@ -20,6 +20,13 @@ enum FallbackIconType { notes, } +enum FilePickerType { + image, + video, + imageAndVideo, + generic, +} + class CryptographyResult { CryptographyResult({ required this.plaintextHash, @@ -61,7 +68,7 @@ class _MoxplatformApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 128: return CryptographyResult.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -82,10 +89,10 @@ class MoxplatformApi { /// Platform APIs Future getPersistentDataPath() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = + await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -109,10 +116,10 @@ class MoxplatformApi { Future getCacheDataPath() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = + await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -136,10 +143,10 @@ class MoxplatformApi { Future openBatteryOptimisationSettings() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = + await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -158,10 +165,10 @@ class MoxplatformApi { Future isIgnoringBatteryOptimizations() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = + await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -184,18 +191,12 @@ class MoxplatformApi { } /// Contacts APIs - Future recordSentMessage(String arg_name, String arg_jid, - String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async { + Future recordSentMessage(String arg_name, String arg_jid, String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send([ - arg_name, - arg_jid, - arg_avatarPath, - arg_fallbackIcon.index - ]) as List?; + final List? replyList = + await channel.send([arg_name, arg_jid, arg_avatarPath, arg_fallbackIcon.index]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -213,25 +214,12 @@ class MoxplatformApi { } /// Cryptography APIs - Future encryptFile( - String arg_sourcePath, - String arg_destPath, - Uint8List arg_key, - Uint8List arg_iv, - CipherAlgorithm arg_algorithm, - String arg_hashSpec) async { + Future encryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send([ - arg_sourcePath, - arg_destPath, - arg_key, - arg_iv, - arg_algorithm.index, - arg_hashSpec - ]) as List?; + final List? replyList = + await channel.send([arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -248,25 +236,12 @@ class MoxplatformApi { } } - Future decryptFile( - String arg_sourcePath, - String arg_destPath, - Uint8List arg_key, - Uint8List arg_iv, - CipherAlgorithm arg_algorithm, - String arg_hashSpec) async { + Future decryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send([ - arg_sourcePath, - arg_destPath, - arg_key, - arg_iv, - arg_algorithm.index, - arg_hashSpec - ]) as List?; + final List? replyList = + await channel.send([arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -283,14 +258,12 @@ class MoxplatformApi { } } - Future hashFile( - String arg_sourcePath, String arg_hashSpec) async { + Future hashFile(String arg_sourcePath, String arg_hashSpec) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_sourcePath, arg_hashSpec]) as List?; + final List? replyList = + await channel.send([arg_sourcePath, arg_hashSpec]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -308,14 +281,12 @@ class MoxplatformApi { } /// Media APIs - Future generateVideoThumbnail( - String arg_src, String arg_dest, int arg_maxWidth) async { + Future generateVideoThumbnail(String arg_src, String arg_dest, int arg_maxWidth) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail', - codec, + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel - .send([arg_src, arg_dest, arg_maxWidth]) as List?; + final List? replyList = + await channel.send([arg_src, arg_dest, arg_maxWidth]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -336,4 +307,32 @@ class MoxplatformApi { return (replyList[0] as bool?)!; } } + + /// Picker + Future> pickFiles(FilePickerType arg_type, bool arg_pickMultiple) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.pickFiles', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_type.index, arg_pickMultiple]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as List?)!.cast(); + } + } } diff --git a/packages/moxplatform_platform_interface/lib/src/platform.dart b/packages/moxplatform_platform_interface/lib/src/platform.dart index 3f4bd7d..162149f 100644 --- a/packages/moxplatform_platform_interface/lib/src/platform.dart +++ b/packages/moxplatform_platform_interface/lib/src/platform.dart @@ -1,3 +1,5 @@ +import 'package:moxplatform_platform_interface/src/api.g.dart'; + abstract class PlatformImplementation { /// Returns the path where persistent data should be stored. Future getPersistentDataPath(); @@ -17,4 +19,6 @@ abstract class PlatformImplementation { /// aspect ratio in tact to [width], and write it to [dest]. If we were successful, returns true. /// If no thumbnail was generated, returns false. Future generateVideoThumbnail(String src, String dest, int width); + + Future> pickFiles(FilePickerType type, bool pickMultiple); } diff --git a/packages/moxplatform_platform_interface/lib/src/platform_stub.dart b/packages/moxplatform_platform_interface/lib/src/platform_stub.dart index b783320..38a028d 100644 --- a/packages/moxplatform_platform_interface/lib/src/platform_stub.dart +++ b/packages/moxplatform_platform_interface/lib/src/platform_stub.dart @@ -1,3 +1,4 @@ +import 'package:moxplatform_platform_interface/src/api.g.dart'; import 'package:moxplatform_platform_interface/src/platform.dart'; class StubPlatformImplementation extends PlatformImplementation { @@ -22,4 +23,7 @@ class StubPlatformImplementation extends PlatformImplementation { int width, ) async => false; + + @override + Future> pickFiles(FilePickerType type, bool pickMultiple) async => []; } diff --git a/pigeons/api.dart b/pigeons/api.dart index aed4075..0aad283 100644 --- a/pigeons/api.dart +++ b/pigeons/api.dart @@ -32,6 +32,13 @@ enum FallbackIconType { notes; } +enum FilePickerType { + image, + video, + imageAndVideo, + generic, +} + @HostApi() abstract class MoxplatformApi { /// Platform APIs @@ -50,4 +57,8 @@ abstract class MoxplatformApi { /// Media APIs bool generateVideoThumbnail(String src, String dest, int maxWidth); + + /// Picker + @async + List pickFiles(FilePickerType type, bool pickMultiple); }