From 1916a9accec48c150821451e6bc4f7dcb625be1c Mon Sep 17 00:00:00 2001 From: Miguel Ruivo Date: Mon, 7 Sep 2020 20:06:18 +0100 Subject: [PATCH] Refactors plugin to use single package with addition of PlatformFile and FilePickerResult classes --- .../plugin/filepicker/FilePickerDelegate.java | 11 +- file_picker/example/README.md | 16 ++ file_picker/example/android/app/build.gradle | 2 +- .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 2 +- .../filepickerexample/MainActivity.java | 2 +- .../mr/flutter/plugin/example/MainActivity.kt | 6 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/ios/Flutter/.last_build_id | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 2 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../lib/generated_plugin_registrant.dart | 16 ++ .../example/lib/src/file_picker_demo.dart | 169 ++++++++---------- file_picker/example/test/widget_test.dart | 30 ++++ file_picker/example/web/favicon.png | Bin 0 -> 917 bytes file_picker/example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes file_picker/example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes file_picker/example/web/index.html | 33 ++++ file_picker/example/web/manifest.json | 23 +++ file_picker/ios/Classes/FilePickerPlugin.m | 11 +- file_picker/lib/file_picker.dart | 134 +------------- file_picker/lib/src/file_picker.dart | 75 ++++++++ file_picker/lib/src/file_picker_io.dart | 86 +++++++++ file_picker/lib/src/file_picker_result.dart | 26 +++ file_picker/lib/src/file_picker_web.dart | 85 +++++++++ file_picker/lib/src/platform_file.dart | 40 +++++ file_picker/pubspec.yaml | 10 +- file_picker_platform_interface/CHANGELOG.md | 4 + file_picker_platform_interface/README.md | 5 + file_picker_platform_interface/pubspec.yaml | 2 +- file_picker_web/CHANGELOG.md | 4 + file_picker_web/README.md | 4 + file_picker_web/pubspec.yaml | 4 +- 36 files changed, 595 insertions(+), 252 deletions(-) create mode 100644 file_picker/example/README.md create mode 100644 file_picker/example/android/app/src/debug/AndroidManifest.xml create mode 100644 file_picker/example/android/app/src/main/kotlin/com/mr/flutter/plugin/example/MainActivity.kt create mode 100644 file_picker/example/android/app/src/profile/AndroidManifest.xml create mode 100644 file_picker/example/ios/Flutter/.last_build_id create mode 100644 file_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 file_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 file_picker/example/ios/Runner/AppDelegate.swift create mode 100644 file_picker/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 file_picker/example/lib/generated_plugin_registrant.dart create mode 100644 file_picker/example/test/widget_test.dart create mode 100644 file_picker/example/web/favicon.png create mode 100644 file_picker/example/web/icons/Icon-192.png create mode 100644 file_picker/example/web/icons/Icon-512.png create mode 100644 file_picker/example/web/index.html create mode 100644 file_picker/example/web/manifest.json create mode 100644 file_picker/lib/src/file_picker.dart create mode 100644 file_picker/lib/src/file_picker_io.dart create mode 100644 file_picker/lib/src/file_picker_result.dart create mode 100644 file_picker/lib/src/file_picker_web.dart create mode 100644 file_picker/lib/src/platform_file.dart diff --git a/file_picker/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerDelegate.java b/file_picker/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerDelegate.java index b89cdb8..27b70fa 100644 --- a/file_picker/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerDelegate.java +++ b/file_picker/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerDelegate.java @@ -18,6 +18,7 @@ import androidx.core.app.ActivityCompat; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel; @@ -100,11 +101,9 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener Log.i(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath()); currentItem++; } - if (paths.size() > 1) { - finishWithSuccess(paths); - } else { - finishWithSuccess(paths.get(0)); - } + + finishWithSuccess(paths); + } else if (data.getData() != null) { Uri uri = data.getData(); String fullPath; @@ -125,7 +124,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener if (fullPath != null) { Log.i(FilePickerDelegate.TAG, "Absolute file path:" + fullPath); - finishWithSuccess(fullPath); + finishWithSuccess(Arrays.asList(fullPath)); } else { finishWithError("unknown_path", "Failed to retrieve path."); } diff --git a/file_picker/example/README.md b/file_picker/example/README.md new file mode 100644 index 0000000..a135626 --- /dev/null +++ b/file_picker/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/file_picker/example/android/app/build.gradle b/file_picker/example/android/app/build.gradle index ae8e6ae..97913c1 100644 --- a/file_picker/example/android/app/build.gradle +++ b/file_picker/example/android/app/build.gradle @@ -22,7 +22,7 @@ android { } defaultConfig { - applicationId "com.mr.flutter.plugin.filepickerexample" + applicationId "com.mr.flutter.plugin.filepicker.example" minSdkVersion 16 targetSdkVersion 29 versionCode 1 diff --git a/file_picker/example/android/app/src/debug/AndroidManifest.xml b/file_picker/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..66063d7 --- /dev/null +++ b/file_picker/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/file_picker/example/android/app/src/main/AndroidManifest.xml b/file_picker/example/android/app/src/main/AndroidManifest.xml index 91966c1..6365b83 100644 --- a/file_picker/example/android/app/src/main/AndroidManifest.xml +++ b/file_picker/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + + diff --git a/file_picker/example/ios/Flutter/.last_build_id b/file_picker/example/ios/Flutter/.last_build_id new file mode 100644 index 0000000..0b85c88 --- /dev/null +++ b/file_picker/example/ios/Flutter/.last_build_id @@ -0,0 +1 @@ +0915ff87e81a3bfb122df4ced418a2b0 \ No newline at end of file diff --git a/file_picker/example/ios/Runner.xcodeproj/project.pbxproj b/file_picker/example/ios/Runner.xcodeproj/project.pbxproj index 97a44e0..039ac22 100644 --- a/file_picker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/file_picker/example/ios/Runner.xcodeproj/project.pbxproj @@ -310,7 +310,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -366,7 +365,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/file_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/file_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/file_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/file_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/file_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/file_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/file_picker/example/ios/Runner/AppDelegate.swift b/file_picker/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/file_picker/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/file_picker/example/ios/Runner/Runner-Bridging-Header.h b/file_picker/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/file_picker/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/file_picker/example/lib/generated_plugin_registrant.dart b/file_picker/example/lib/generated_plugin_registrant.dart new file mode 100644 index 0000000..daa72e8 --- /dev/null +++ b/file_picker/example/lib/generated_plugin_registrant.dart @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// ignore: unused_import +import 'dart:ui'; + +import 'package:file_picker/src/file_picker_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(PluginRegistry registry) { + FilePickerWeb.registerWith(registry.registrarFor(FilePickerWeb)); + registry.registerMessageHandler(); +} diff --git a/file_picker/example/lib/src/file_picker_demo.dart b/file_picker/example/lib/src/file_picker_demo.dart index 47ac026..99f61ee 100644 --- a/file_picker/example/lib/src/file_picker_demo.dart +++ b/file_picker/example/lib/src/file_picker_demo.dart @@ -5,19 +5,18 @@ import 'package:file_picker/file_picker.dart'; class FilePickerDemo extends StatefulWidget { @override - _FilePickerDemoState createState() => new _FilePickerDemoState(); + _FilePickerDemoState createState() => _FilePickerDemoState(); } class _FilePickerDemoState extends State { final GlobalKey _scaffoldKey = GlobalKey(); String _fileName; - String _path; - Map _paths; + List _paths; String _extension; bool _loadingPath = false; bool _multiPick = false; FileType _pickingType = FileType.any; - TextEditingController _controller = new TextEditingController(); + TextEditingController _controller = TextEditingController(); @override void initState() { @@ -28,97 +27,84 @@ class _FilePickerDemoState extends State { void _openFileExplorer() async { setState(() => _loadingPath = true); try { - if (_multiPick) { - _path = null; - _paths = await FilePicker.getMultiFilePath( - type: _pickingType, - allowedExtensions: (_extension?.isNotEmpty ?? false) - ? _extension?.replaceAll(' ', '')?.split(',') - : null, - ); - } else { - _paths = null; - _path = await FilePicker.getFilePath( - type: _pickingType, - allowedExtensions: (_extension?.isNotEmpty ?? false) - ? _extension?.replaceAll(' ', '')?.split(',') - : null, - ); - } + _paths = (await FilePicker.instance.pickFiles( + type: _pickingType, + allowMultiple: _multiPick, + allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null, + )) + ?.files; } on PlatformException catch (e) { print("Unsupported operation" + e.toString()); + } catch (ex) { + print(ex); } if (!mounted) return; setState(() { _loadingPath = false; - _fileName = _path != null - ? _path.split('/').last - : _paths != null ? _paths.keys.toString() : '...'; + _fileName = _paths != null ? _paths.map((e) => e.name).toString() : '...'; }); } void _clearCachedFiles() { - FilePicker.clearTemporaryFiles().then((result) { + FilePicker.instance.clearTemporaryFiles().then((result) { _scaffoldKey.currentState.showSnackBar( SnackBar( backgroundColor: result ? Colors.green : Colors.red, - content: Text((result - ? 'Temporary files removed with success.' - : 'Failed to clean temporary files')), + content: Text((result ? 'Temporary files removed with success.' : 'Failed to clean temporary files')), ), ); }); } void _selectFolder() { - FilePicker.getDirectoryPath().then((value) { - setState(() => _path = value); + FilePicker.instance.getDirectoryPath().then((value) { + setState(() => _paths = [value]); }); } @override Widget build(BuildContext context) { - return new MaterialApp( - home: new Scaffold( + return MaterialApp( + home: Scaffold( key: _scaffoldKey, - appBar: new AppBar( + appBar: AppBar( title: const Text('File Picker example app'), ), - body: new Center( - child: new Padding( + body: Center( + child: Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0), - child: new SingleChildScrollView( - child: new Column( + child: SingleChildScrollView( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - new Padding( + Padding( padding: const EdgeInsets.only(top: 20.0), - child: new DropdownButton( - hint: new Text('LOAD PATH FROM'), + child: DropdownButton( + hint: Text('LOAD PATH FROM'), value: _pickingType, items: [ - new DropdownMenuItem( - child: new Text('FROM AUDIO'), + DropdownMenuItem( + child: Text('FROM AUDIO'), value: FileType.audio, ), - new DropdownMenuItem( - child: new Text('FROM IMAGE'), + DropdownMenuItem( + child: Text('FROM IMAGE'), value: FileType.image, ), - new DropdownMenuItem( - child: new Text('FROM VIDEO'), + DropdownMenuItem( + child: Text('FROM VIDEO'), value: FileType.video, ), - new DropdownMenuItem( - child: new Text('FROM MEDIA'), + DropdownMenuItem( + child: Text('FROM MEDIA'), value: FileType.media, ), - new DropdownMenuItem( - child: new Text('FROM ANY'), + DropdownMenuItem( + child: Text('FROM ANY'), value: FileType.any, ), - new DropdownMenuItem( - child: new Text('CUSTOM FORMAT'), + DropdownMenuItem( + child: Text('CUSTOM FORMAT'), value: FileType.custom, ), ], @@ -129,87 +115,76 @@ class _FilePickerDemoState extends State { } })), ), - new ConstrainedBox( - constraints: BoxConstraints.tightFor(width: 100.0), + ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: 100.0), child: _pickingType == FileType.custom - ? new TextFormField( + ? TextFormField( maxLength: 15, autovalidate: true, controller: _controller, - decoration: - InputDecoration(labelText: 'File extension'), + decoration: InputDecoration(labelText: 'File extension'), keyboardType: TextInputType.text, textCapitalization: TextCapitalization.none, ) - : new Container(), + : const SizedBox(), ), - new ConstrainedBox( - constraints: BoxConstraints.tightFor(width: 200.0), - child: new SwitchListTile.adaptive( - title: new Text('Pick multiple files', - textAlign: TextAlign.right), - onChanged: (bool value) => - setState(() => _multiPick = value), + ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: 200.0), + child: SwitchListTile.adaptive( + title: Text('Pick multiple files', textAlign: TextAlign.right), + onChanged: (bool value) => setState(() => _multiPick = value), value: _multiPick, ), ), - new Padding( + Padding( padding: const EdgeInsets.only(top: 50.0, bottom: 20.0), child: Column( children: [ - new RaisedButton( + RaisedButton( onPressed: () => _openFileExplorer(), - child: new Text("Open file picker"), + child: Text("Open file picker"), ), - new RaisedButton( + RaisedButton( onPressed: () => _selectFolder(), - child: new Text("Pick folder"), + child: Text("Pick folder"), ), - new RaisedButton( + RaisedButton( onPressed: () => _clearCachedFiles(), - child: new Text("Clear temporary files"), + child: Text("Clear temporary files"), ), ], ), ), - new Builder( + Builder( builder: (BuildContext context) => _loadingPath ? Padding( padding: const EdgeInsets.only(bottom: 10.0), - child: const CircularProgressIndicator()) - : _path != null || _paths != null - ? new Container( + child: const CircularProgressIndicator(), + ) + : _paths != null + ? Container( padding: const EdgeInsets.only(bottom: 30.0), height: MediaQuery.of(context).size.height * 0.50, - child: new Scrollbar( - child: new ListView.separated( - itemCount: _paths != null && _paths.isNotEmpty - ? _paths.length - : 1, + child: Scrollbar( + child: ListView.separated( + itemCount: _paths != null && _paths.isNotEmpty ? _paths.length : 1, itemBuilder: (BuildContext context, int index) { - final bool isMultiPath = - _paths != null && _paths.isNotEmpty; - final String name = 'File $index: ' + - (isMultiPath - ? _paths.keys.toList()[index] - : _fileName ?? '...'); - final path = isMultiPath - ? _paths.values.toList()[index].toString() - : _path; + final bool isMultiPath = _paths != null && _paths.isNotEmpty; + final String name = + 'File $index: ' + (isMultiPath ? _paths.map((e) => e.name).toList()[index] : _fileName ?? '...'); + final path = _paths.map((e) => e.path).toList()[index].toString(); - return new ListTile( - title: new Text( + return ListTile( + title: Text( name, ), - subtitle: new Text(path), + subtitle: Text(path), ); }, - separatorBuilder: - (BuildContext context, int index) => - new Divider(), + separatorBuilder: (BuildContext context, int index) => const Divider(), )), ) - : new Container(), + : const SizedBox(), ), ], ), diff --git a/file_picker/example/test/widget_test.dart b/file_picker/example/test/widget_test.dart new file mode 100644 index 0000000..747db1d --- /dev/null +++ b/file_picker/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/file_picker/example/web/favicon.png b/file_picker/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/file_picker/example/web/icons/Icon-192.png b/file_picker/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/file_picker/example/web/icons/Icon-512.png b/file_picker/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/file_picker/example/web/index.html b/file_picker/example/web/index.html new file mode 100644 index 0000000..9b7a438 --- /dev/null +++ b/file_picker/example/web/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + example + + + + + + + + diff --git a/file_picker/example/web/manifest.json b/file_picker/example/web/manifest.json new file mode 100644 index 0000000..c638001 --- /dev/null +++ b/file_picker/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "minimal-ui", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/file_picker/ios/Classes/FilePickerPlugin.m b/file_picker/ios/Classes/FilePickerPlugin.m index ab65a33..72738aa 100644 --- a/file_picker/ios/Classes/FilePickerPlugin.m +++ b/file_picker/ios/Classes/FilePickerPlugin.m @@ -253,7 +253,7 @@ - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url{ [self.documentPickerController dismissViewControllerAnimated:YES completion:nil]; NSString * path = (NSString *)[url path]; - _result(path); + _result(@[path]); _result = nil; } @@ -267,12 +267,7 @@ didPickDocumentsAtURLs:(NSArray *)urls{ [self.documentPickerController dismissViewControllerAnimated:YES completion:nil]; NSArray * result = [FileUtils resolvePath:urls]; - - if([result count] > 1) { - _result(result); - } else { - _result([result objectAtIndex:0]); - } + _result(result); _result = nil; } @@ -323,7 +318,7 @@ didPickDocumentsAtURLs:(NSArray *)urls{ return; } - _result([pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]); + _result(@[[pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]]); _result = nil; } diff --git a/file_picker/lib/file_picker.dart b/file_picker/lib/file_picker.dart index 96c63de..7a75eb1 100644 --- a/file_picker/lib/file_picker.dart +++ b/file_picker/lib/file_picker.dart @@ -1,131 +1,5 @@ -import 'dart:async'; -import 'dart:io'; +library file_picker; -import 'package:file_picker_platform_interface/file_picker_platform_interface.dart'; -import 'package:file_picker_platform_interface/method_channel_file_picker.dart'; - -export 'package:file_picker_platform_interface/file_picker_platform_interface.dart' - show FileType; - -final MethodChannelFilePicker _filePickerPlatform = FilePickerPlatform.instance; - -class FilePicker { - FilePicker._(); - - /// Returns an absolute file path from the calling platform. - /// - /// Extension filters are allowed with [FileType.custom], when used, make sure to provide a [List] - /// of `allowedExtensions` (e.g. [`pdf`, `svg`, `jpg`].). - /// - /// If you want to track picking status, for example, because some files may take some time to be - /// cached (particularly those picked from cloud providers), you may want to set [onFileLoading] handler - /// that will give you the current status of picking. - /// - /// If you plan on picking images/videos and don't want them to be compressed automatically by OS, - /// you should set `allowCompression` to [false]. Calling this on Android won't have any effect, as - /// it already provides you the original file (or integral copy). - /// - /// Defaults to [FileType.any] which will display all file types. - static Future getFilePath({ - FileType type = FileType.any, - List allowedExtensions, - Function(FilePickerStatus) onFileLoading, - bool allowCompression, - }) async => - await _filePickerPlatform.getFiles( - type: type, - allowedExtensions: allowedExtensions, - onFileLoading: onFileLoading, - allowCompression: allowCompression, - ); - - /// Returns an iterable [Map] where the `key` is the name of the file - /// and the `value` the path. - /// - /// A [List] with `allowedExtensions` can be provided to filter the allowed files to picked. - /// If provided, make sure you select [FileType.custom] as type. - /// - /// If you want to track picking status, for example, because some files may take some time to be - /// cached (particularly those picked from cloud providers), you may want to set `onFileLoading` handler - /// that will give you the current status of picking. - /// - /// If you plan on picking images/videos and don't want them to be compressed automatically by OS, - /// you should set `allowCompression` to [false]. Calling this on Android won't have any effect, as - /// it already provides you the original file (or integral copy). - /// - /// Defaults to `FileType.any`, which allows any combination of files to be multi selected at once. - static Future> getMultiFilePath({ - FileType type = FileType.any, - List allowedExtensions, - Function(FilePickerStatus) onFileLoading, - bool allowCompression, - }) async => - await _filePickerPlatform.getFiles( - type: type, - allowMultiple: true, - allowedExtensions: allowedExtensions, - onFileLoading: onFileLoading, - allowCompression: allowCompression, - ); - - /// Returns a [File] object from the selected file path. - /// - /// This is an utility method that does the same of [getFilePath] but saving some boilerplate if - /// you are planing to create a [File] for the returned path. - static Future getFile({ - FileType type = FileType.any, - List allowedExtensions, - Function(FilePickerStatus) onFileLoading, - bool allowCompression, - }) async { - final String filePath = await _filePickerPlatform.getFiles( - type: type, - allowedExtensions: allowedExtensions, - onFileLoading: onFileLoading, - allowCompression: allowCompression, - ); - return filePath != null ? File(filePath) : null; - } - - /// Returns a [List] object from the selected files paths. - /// - /// This is an utility method that does the same of [getMultiFilePath] but saving some boilerplate if - /// you are planing to create a list of [File]`s for the returned paths. - static Future> getMultiFile({ - FileType type = FileType.any, - List allowedExtensions, - Function(FilePickerStatus) onFileLoading, - bool allowCompression, - }) async { - final Map paths = await _filePickerPlatform.getFiles( - type: type, - allowMultiple: true, - allowedExtensions: allowedExtensions, - onFileLoading: onFileLoading, - allowCompression: allowCompression, - ); - - return paths != null && paths.isNotEmpty - ? paths.values.map((path) => File(path)).toList() - : null; - } - - /// Selects a directory and returns its absolute path. - /// - /// On Android, this requires to be running on SDK 21 or above, else won't work. - /// Returns [null] if folder path couldn't be resolved. - static Future getDirectoryPath() async { - return _filePickerPlatform.getDirectoryPath(); - } - - /// Asks the underlying platform to remove any temporary files created by this plugin. - /// - /// This typically relates to cached files that are stored in the cache directory of - /// each platform and it isn't required to invoke this as the system should take care - /// of it whenever needed. However, this will force the cleanup if you want to manage those on your own. - /// - /// Returns [true] if the files were removed with success, [false] otherwise. - static Future clearTemporaryFiles() async { - return _filePickerPlatform.clearTemporaryFiles(); - } -} +export './src/file_picker.dart'; +export './src/platform_file.dart'; +export './src/file_picker_result.dart'; diff --git a/file_picker/lib/src/file_picker.dart b/file_picker/lib/src/file_picker.dart new file mode 100644 index 0000000..4876dec --- /dev/null +++ b/file_picker/lib/src/file_picker.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:file_picker/file_picker.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'file_picker_io.dart'; +import 'file_picker_result.dart'; + +enum FileType { + any, + media, + image, + video, + audio, + custom, +} + +enum FilePickerStatus { + picking, + done, +} + +/// The interface that implementations of file_picker must implement. +/// +/// Platform implementations should extend this class rather than implement it as `file_picker` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [FilePicker] methods. +abstract class FilePicker extends PlatformInterface { + FilePicker() : super(token: _token); + + static final Object _token = Object(); + + static FilePicker _instance = FilePickerIO(); + + static FilePicker get instance => _instance; + + static set instance(FilePicker instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Retrieves the file(s) from the underlying platform + /// + /// Default [type] set to `FileType.any` with [allowMultiple] set to `false` + /// Optionally, [allowedExtensions] might be provided (e.g. [`pdf`, `svg`, `jpg`].). + /// + /// If you want to track picking status, for example, because some files may take some time to be + /// cached (particularly those picked from cloud providers), you may want to set [onFileLoading] handler + /// that will give you the current status of picking. + Future pickFiles({ + FileType type = FileType.any, + List allowedExtensions, + Function(FilePickerStatus) onFileLoading, + bool allowCompression, + bool allowMultiple = false, + }) async => + throw UnimplementedError('pickFiles() has not been implemented.'); + + /// Asks the underlying platform to remove any temporary files created by this plugin. + /// + /// This typically relates to cached files that are stored in the cache directory of + /// each platform and it isn't required to invoke this as the system should take care + /// of it whenever needed. However, this will force the cleanup if you want to manage those on your own. + /// + /// Returns `true` if the files were removed with success, `false` otherwise. + Future clearTemporaryFiles() async => throw UnimplementedError('clearTemporaryFiles() has not been implemented.'); + + /// Selects a directory and returns its absolute path. + /// + /// On Android, this requires to be running on SDK 21 or above, else won't work. + /// Returns `null` if folder path couldn't be resolved. + Future getDirectoryPath() async => throw UnimplementedError('getDirectoryPath() has not been implemented.'); +} diff --git a/file_picker/lib/src/file_picker_io.dart b/file_picker/lib/src/file_picker_io.dart new file mode 100644 index 0000000..64493ab --- /dev/null +++ b/file_picker/lib/src/file_picker_io.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +import 'package:file_picker/file_picker.dart'; +import 'package:file_picker/src/platform_file.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'file_picker_result.dart'; + +const MethodChannel _channel = MethodChannel('miguelruivo.flutter.plugins.filepicker'); +const EventChannel _eventChannel = EventChannel('miguelruivo.flutter.plugins.filepickerevent'); + +/// An implementation of [FilePicker] that uses method channels. +class FilePickerIO extends FilePicker { + static const String _tag = 'MethodChannelFilePicker'; + static StreamSubscription _eventSubscription; + + @override + Future pickFiles({ + FileType type = FileType.any, + List allowedExtensions, + Function(FilePickerStatus) onFileLoading, + bool allowCompression, + bool allowMultiple = false, + }) => + _getPath(type, allowMultiple, allowCompression, allowedExtensions, onFileLoading); + + @override + Future clearTemporaryFiles() async => _channel.invokeMethod('clear'); + + @override + Future getDirectoryPath() async { + try { + String result = await _channel.invokeMethod('dir', {}); + if (result != null) { + return PlatformFile(path: result, isDirectory: true); + } + } on PlatformException catch (ex) { + if (ex.code == "unknown_path") { + print( + '[$_tag] Could not resolve directory path. Maybe it\'s a protected one or unsupported (such as Downloads folder). If you are on Android, make sure that you are on SDK 21 or above.'); + } + } + return null; + } + + Future _getPath( + FileType fileType, + bool allowMultipleSelection, + bool allowCompression, + List allowedExtensions, + Function(FilePickerStatus) onFileLoading, + ) async { + final String type = describeEnum(fileType); + if (type != 'custom' && (allowedExtensions?.isNotEmpty ?? false)) { + throw Exception('If you are using a custom extension filter, please use the FileType.custom instead.'); + } + try { + _eventSubscription?.cancel(); + if (onFileLoading != null) { + _eventSubscription = _eventChannel.receiveBroadcastStream().listen( + (data) => onFileLoading((data as bool) ? FilePickerStatus.picking : FilePickerStatus.done), + onError: (error) => throw Exception(error), + ); + } + + final List result = await _channel.invokeListMethod(type, { + 'allowMultipleSelection': allowMultipleSelection, + 'allowedExtensions': allowedExtensions, + 'allowCompression': allowCompression, + }); + + if (result == null) { + return null; + } + + return FilePickerResult(result.map((file) => PlatformFile(name: file.split('/').last, path: file)).toList()); + } on PlatformException catch (e) { + print('[$_tag] Platform exception: $e'); + rethrow; + } catch (e) { + print('[$_tag] Unsupported operation. Method not found. The exception thrown was: $e'); + rethrow; + } + } +} diff --git a/file_picker/lib/src/file_picker_result.dart b/file_picker/lib/src/file_picker_result.dart new file mode 100644 index 0000000..b3b5cfc --- /dev/null +++ b/file_picker/lib/src/file_picker_result.dart @@ -0,0 +1,26 @@ +import 'package:file_picker/src/platform_file.dart'; +import 'package:flutter/foundation.dart'; + +class FilePickerResult { + const FilePickerResult(this.files); + + /// Picked files. + final List files; + + /// If this pick contains only a single resource. + bool get isSinglePick => files.length == 1; + + /// The length of picked files. + int get count => files.length; + + /// A `List` containing all paths from picked files. + /// + /// This may or not be available and will typically reference cached copies of + /// original files (which can be accessed through its URI property). + /// + /// Only available on IO. Throws `UnsupportedError` on Web. + List get paths => files.map((file) => kIsWeb ? throw UnsupportedError('Unsupported on Web') : file.path).toList(); + + /// A `List` containing all names from picked files with its extensions. + List get names => files.map((file) => file.name).toList(); +} diff --git a/file_picker/lib/src/file_picker_web.dart b/file_picker/lib/src/file_picker_web.dart new file mode 100644 index 0000000..c4a336e --- /dev/null +++ b/file_picker/lib/src/file_picker_web.dart @@ -0,0 +1,85 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:html' as html; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'file_picker_result.dart'; +import 'platform_file.dart'; + +class FilePickerWeb extends FilePicker { + FilePickerWeb._(); + static final FilePickerWeb platform = FilePickerWeb._(); + + static void registerWith(Registrar registrar) { + FilePicker.instance = platform; + } + + @override + Future pickFiles({ + FileType type = FileType.any, + List allowedExtensions, + bool allowMultiple = false, + Function(FilePickerStatus) onFileLoading, + bool allowCompression, + }) async { + final Completer> filesCompleter = Completer>(); + + String accept = _fileType(type, allowedExtensions); + html.InputElement uploadInput = html.FileUploadInputElement(); + uploadInput.draggable = true; + uploadInput.multiple = allowMultiple; + uploadInput.accept = accept; + uploadInput.click(); + + uploadInput.onChange.listen((e) { + final files = uploadInput.files; + final reader = html.FileReader(); + + List pickedFiles = []; + + reader.onLoadEnd.listen((e) { + pickedFiles.add( + PlatformFile( + name: uploadInput.value.replaceAll('\\', '/'), + bytes: Base64Decoder().convert(reader.result.toString().split(",").last), + ), + ); + + if (pickedFiles.length >= files.length) { + filesCompleter.complete(pickedFiles); + } + }); + + files.forEach((element) { + reader.readAsDataUrl(element); + }); + }); + return FilePickerResult(await filesCompleter.future); + } + + static String _fileType(FileType type, List allowedExtensions) { + switch (type) { + case FileType.any: + return ''; + + case FileType.audio: + return 'audio/*'; + + case FileType.image: + return 'image/*'; + + case FileType.video: + return 'video/*'; + + case FileType.media: + return 'video/*|image/*'; + + case FileType.custom: + return allowedExtensions.fold('', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next'); + break; + } + return ''; + } +} diff --git a/file_picker/lib/src/platform_file.dart b/file_picker/lib/src/platform_file.dart new file mode 100644 index 0000000..cbefcb6 --- /dev/null +++ b/file_picker/lib/src/platform_file.dart @@ -0,0 +1,40 @@ +import 'dart:typed_data'; + +class PlatformFile { + PlatformFile({ + this.path, + this.uri, + this.name, + this.bytes, + this.isDirectory = false, + }); + + /// The absolute path for this file instance. + /// + /// Typically whis will reflect a copy cached file and not the original source, + /// also, it's not guaranteed that this path is always available as some files + /// can be protected by OS. + /// + /// Available on IO only. On Web is always `null`. + final String path; + + /// The URI (Universal Resource Identifier) for this file. + /// + /// This is the original file resource identifier and can be used to + /// manipulate the original file (read, write, delete). + /// + /// Available on IO only. On Web is always `null`. + final String uri; + + /// File name including its extension. + final String name; + + /// Byte data for this file. + final Uint8List bytes; + + /// Whether this file references a directory or not. + final bool isDirectory; + + /// File extension for this file. + String get extension => name.split('/').last; +} diff --git a/file_picker/pubspec.yaml b/file_picker/pubspec.yaml index 2940e87..22e4485 100644 --- a/file_picker/pubspec.yaml +++ b/file_picker/pubspec.yaml @@ -1,13 +1,16 @@ name: file_picker description: A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support. homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker -version: 1.13.3 +version: 2.0.0 dependencies: flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter + flutter_plugin_android_lifecycle: ^1.0.6 - file_picker_platform_interface: ^1.3.1 + plugin_platform_interface: ^1.0.1 environment: sdk: ">=2.0.0 <3.0.0" @@ -22,4 +25,5 @@ flutter: ios: pluginClass: FilePickerPlugin web: - default_package: file_picker_web + pluginClass: FilePickerWeb + fileName: src/file_picker_web.dart diff --git a/file_picker_platform_interface/CHANGELOG.md b/file_picker_platform_interface/CHANGELOG.md index 75f0c6d..978f2d6 100644 --- a/file_picker_platform_interface/CHANGELOG.md +++ b/file_picker_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0] - Deprecates interface + +Deprecates interface in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms. + ## [1.3.1] - Rollback `allowCompression` Removes `allowCompression` from interface as it should only be used from `file_picker` (Android & iOS). diff --git a/file_picker_platform_interface/README.md b/file_picker_platform_interface/README.md index 9b8f456..68d2f37 100644 --- a/file_picker_platform_interface/README.md +++ b/file_picker_platform_interface/README.md @@ -1,3 +1,7 @@ +# MUST READ! + +The interface is deprectated in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms where an interface is integrated. This should be the one used as this package is not longer mantained. + # file_picker_platform_interface A common platform interface for the [`file_picker`][1] plugin. @@ -6,6 +10,7 @@ This interface allows platform-specific implementations of the `file_picker` plugin, as well as the plugin itself, to ensure they are supporting the same interface. + # Usage To implement a new platform-specific implementation of `file_picker`, extend diff --git a/file_picker_platform_interface/pubspec.yaml b/file_picker_platform_interface/pubspec.yaml index 74bf79f..ccf6174 100644 --- a/file_picker_platform_interface/pubspec.yaml +++ b/file_picker_platform_interface/pubspec.yaml @@ -1,7 +1,7 @@ name: file_picker_platform_interface description: A common platform interface for the file_picker plugin that must be used to share commom features homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker/file_picker_platform_interface -version: 1.3.1 +version: 2.0.0 environment: sdk: ">=2.1.0 <3.0.0" diff --git a/file_picker_web/CHANGELOG.md b/file_picker_web/CHANGELOG.md index 0bd9387..ea47c14 100644 --- a/file_picker_web/CHANGELOG.md +++ b/file_picker_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0 + +Deprecates plugin in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms. + ## 1.0.2+1 Fix custom filter String creation. diff --git a/file_picker_web/README.md b/file_picker_web/README.md index 9223183..30cd96e 100644 --- a/file_picker_web/README.md +++ b/file_picker_web/README.md @@ -1,3 +1,7 @@ +# MUST READ! + +The web standalone plugin is deprectated in favor of standalone [file_picker](https://pub.dev/packages/file_picker) for all platforms. This should be the one used as this package is not longer mantained. + # file_picker_web The web implementation of [`file_picker`][1]. diff --git a/file_picker_web/pubspec.yaml b/file_picker_web/pubspec.yaml index 03def2f..9001480 100644 --- a/file_picker_web/pubspec.yaml +++ b/file_picker_web/pubspec.yaml @@ -1,14 +1,14 @@ name: file_picker_web description: Web platform implementation of file_picker. Provides a way to pick files with filter support for Web. homepage: https://github.com/miguelpruivo/flutter_file_picker/tree/master/file_picker_web -version: 1.0.2+1 +version: 2.0.0 environment: sdk: ">=2.7.0 <3.0.0" flutter: ">=1.10.0" dependencies: - file_picker_platform_interface: ^1.3.1 + file_picker_platform_interface: ^2.0.0 flutter: sdk: flutter flutter_web_plugins: