12
CHANGELOG.md
|
@ -1,3 +1,15 @@
|
|||
## 4.0.0
|
||||
### Desktop support added for all platforms (MacOS, Linux & Windows) ([#271](https://github.com/miguelpruivo/flutter_file_picker/issues/271)) 🎉
|
||||
From now on, you'll be able to use file_picker with all your platforms, a big thanks to @philenius, which made this possible and allowed the [flutter_file_picker_desktop](https://github.com/philenius/flutter_file_picker_desktop) to be merged with this one.
|
||||
|
||||
Have in mind that because of platforms differences, that the following API methods aren't available to use on Desktop:
|
||||
- The `onFileLoading()` isn't necessary, hence, `FilePickerStatus` won't change, since it hasn't any effect on those;
|
||||
- `clearTemporaryFiles()` isn't necessary since those files aren't created — the platforms will always use a reference to the original file;
|
||||
- There is a new optional parameter `dialogTitle` which can be used to set the title of the modal dialog when picking the files;
|
||||
|
||||
##### Web
|
||||
Adds `onFileLoading()` to Web. ([#766](https://github.com/miguelpruivo/flutter_file_picker/issues/766)).
|
||||
|
||||
## 3.0.4
|
||||
##### Android
|
||||
- Addresses an issue where an invalid custom file extension wouldn't throw an error when it should. Thank you @Jahn08.
|
||||
|
|
20
README.md
|
@ -12,6 +12,9 @@
|
|||
<a href="https://www.buymeacoffee.com/gQyz2MR">
|
||||
<img alt="Buy me a coffee" src="https://img.shields.io/badge/Donate-Buy%20Me%20A%20Coffee-yellow.svg">
|
||||
</a>
|
||||
<a href="https://github.com/miguelpruivo/flutter_file_picker/issues"><img src="https://img.shields.io/github/issues/miguelpruivo/flutter_file_picker">
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/license/miguelpruivo/flutter_file_picker">
|
||||
</p>
|
||||
|
||||
# File Picker
|
||||
|
@ -19,13 +22,12 @@ A package that allows you to use the native file explorer to pick single or mult
|
|||
|
||||
## Currently supported features
|
||||
* Uses OS default native pickers
|
||||
* Supports multiple platforms (Mobile, Web, Desktop and Flutter GO)
|
||||
* Pick files using **custom format** filtering — you can provide a list of file extensions (pdf, svg, zip, etc.)
|
||||
* Pick files from **cloud files** (GDrive, Dropbox, iCloud)
|
||||
* Single or multiple file picks
|
||||
* Different default type filtering (media, image, video, audio or any)
|
||||
* Picking directories
|
||||
* Flutter Web
|
||||
* Desktop (MacOS, Linux and Windows through Flutter Go)
|
||||
* Picking directories
|
||||
* Load file data immediately into memory (`Uint8List`) if needed;
|
||||
|
||||
If you have any feature that you want to see in this package, please feel free to issue a suggestion. 🎉
|
||||
|
@ -110,8 +112,16 @@ if (result != null) {
|
|||
For full usage details refer to the **[Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** above.
|
||||
|
||||
## Example App
|
||||
![Demo](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/example.gif)
|
||||
![DemoMultiFilters](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/example_ios.gif)
|
||||
#### Android
|
||||
![Demo](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/screenshots/example.gif)
|
||||
#### iOS
|
||||
![DemoMultiFilters](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/screenshots/example_ios.gif)
|
||||
#### MacOS
|
||||
![DemoMacOS](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/screenshots/example_macos.png)
|
||||
#### Linux
|
||||
![DemoLinux](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/screenshots/example_linux.png)
|
||||
#### Windows
|
||||
![DemoWindows](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/screenshots/example_windows.png)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:file_picker/_internal/file_picker_web.dart';
|
||||
import 'package:file_picker/src/file_picker_web.dart';
|
||||
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
|
|
|
@ -4,5 +4,5 @@ import 'package:flutter/widgets.dart';
|
|||
|
||||
void main() {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
|
||||
runApp(new FilePickerDemo());
|
||||
runApp(FilePickerDemo());
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
_paths = (await FilePicker.platform.pickFiles(
|
||||
type: _pickingType,
|
||||
allowMultiple: _multiPick,
|
||||
onFileLoading: (FilePickerStatus status) => print(status),
|
||||
allowedExtensions: (_extension?.isNotEmpty ?? false)
|
||||
? _extension?.replaceAll(' ', '').split(',')
|
||||
: null,
|
||||
|
@ -44,7 +45,6 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
if (!mounted) return;
|
||||
setState(() {
|
||||
_loadingPath = false;
|
||||
print(_paths!.first.extension);
|
||||
_fileName =
|
||||
_paths != null ? _paths!.map((e) => e.name).toString() : '...';
|
||||
});
|
||||
|
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.0 MiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 131 KiB |
After Width: | Height: | Size: 129 KiB |
|
@ -1,11 +1,17 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:file_picker/src/file_picker_io.dart';
|
||||
import 'package:file_picker/src/file_picker_linux.dart';
|
||||
import 'package:file_picker/src/file_picker_macos.dart';
|
||||
import 'package:file_picker/src/windows/stub.dart'
|
||||
if (dart.library.io) 'package:file_picker/src/windows/file_picker_windows.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'file_picker_io.dart';
|
||||
import 'file_picker_result.dart';
|
||||
|
||||
const String defaultDialogTitle = 'File Picker';
|
||||
|
||||
enum FileType {
|
||||
any,
|
||||
media,
|
||||
|
@ -32,7 +38,7 @@ abstract class FilePicker extends PlatformInterface {
|
|||
|
||||
static final Object _token = Object();
|
||||
|
||||
static FilePicker _instance = FilePickerIO();
|
||||
static late FilePicker _instance = _setPlatform();
|
||||
|
||||
static FilePicker get platform => _instance;
|
||||
|
||||
|
@ -41,33 +47,53 @@ abstract class FilePicker extends PlatformInterface {
|
|||
_instance = instance;
|
||||
}
|
||||
|
||||
static FilePicker _setPlatform() {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
return FilePickerIO();
|
||||
} else if (Platform.isLinux) {
|
||||
return FilePickerLinux();
|
||||
} else if (Platform.isWindows) {
|
||||
return filePickerWithFFI();
|
||||
} else if (Platform.isMacOS) {
|
||||
return FilePickerMacOS();
|
||||
} else {
|
||||
throw UnimplementedError(
|
||||
'The current platform "${Platform.operatingSystem}" is not supported by this plugin.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]`.).
|
||||
/// Default `type` set to [FileType.any] with `allowMultiple` set to `false`.
|
||||
/// Optionally, `allowedExtensions` might be provided (e.g. `[pdf, svg, jpg]`.).
|
||||
///
|
||||
/// If [withData] is set, picked files will have its byte data immediately available on memory as `Uint8List`
|
||||
/// If `withData` is set, picked files will have its byte data immediately available on memory as `Uint8List`
|
||||
/// which can be useful if you are picking it for server upload or similar. However, have in mind that
|
||||
/// enabling this on IO (iOS & Android) may result in out of memory issues if you allow multiple picks or
|
||||
/// pick huge files. Use [withReadStream] instead. Defaults to `true` on web, `false` otherwise.
|
||||
/// pick huge files. Use `withReadStream` instead. Defaults to `true` on web, `false` otherwise.
|
||||
///
|
||||
/// If [withReadStream] is set, picked files will have its byte data available as a [Stream<List<int>>]
|
||||
/// If `withReadStream` is set, picked files will have its byte data available as a `Stream<List<int>>`
|
||||
/// which can be useful for uploading and processing large files. Defaults to `false`.
|
||||
///
|
||||
/// 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 [allowCompression] is set, it will allow media to apply the default OS compression.
|
||||
/// If `allowCompression` is set, it will allow media to apply the default OS compression.
|
||||
/// Defaults to `true`.
|
||||
///
|
||||
/// The result is wrapped in a [FilePickerResult] which contains helper getters
|
||||
/// with useful information regarding the picked [List<PlatformFile>].
|
||||
/// `dialogTitle` can be optionally set on desktop platforms to set the modal window title. It will be ignored on
|
||||
/// other platforms.
|
||||
///
|
||||
/// The result is wrapped in a `FilePickerResult` which contains helper getters
|
||||
/// with useful information regarding the picked `List<PlatformFile>`.
|
||||
///
|
||||
/// For more information, check the [API documentation](https://github.com/miguelpruivo/flutter_file_picker/wiki/api).
|
||||
///
|
||||
/// Returns [null] if aborted.
|
||||
/// Returns `null` if aborted.
|
||||
Future<FilePickerResult?> pickFiles({
|
||||
String? dialogTitle,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
Function(FilePickerStatus)? onFileLoading,
|
||||
|
@ -84,16 +110,20 @@ abstract class FilePicker extends PlatformInterface {
|
|||
/// 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.
|
||||
/// This method is only available on mobile platforms (Android & iOS).
|
||||
///
|
||||
/// Returns `true` if the files were removed with success, `false` otherwise.
|
||||
Future<bool?> 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.
|
||||
/// Returns `null` if folder path couldn't be resolved.
|
||||
///
|
||||
/// `dialogTitle` can be set to display a custom title on desktop platforms. It will be ignored on Web & IO.
|
||||
///
|
||||
/// Note: Some Android paths are protected, hence can't be accessed and will return `/` instead.
|
||||
Future<String?> getDirectoryPath() async =>
|
||||
Future<String?> getDirectoryPath({String? dialogTitle}) async =>
|
||||
throw UnimplementedError('getDirectoryPath() has not been implemented.');
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ import 'package:file_picker/src/platform_file.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'file_picker_result.dart';
|
||||
|
||||
final MethodChannel _channel = MethodChannel(
|
||||
'miguelruivo.flutter.plugins.filepicker',
|
||||
Platform.isLinux || Platform.isWindows || Platform.isMacOS
|
||||
|
@ -27,6 +25,7 @@ class FilePickerIO extends FilePicker {
|
|||
Future<FilePickerResult?> pickFiles({
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
String? dialogTitle,
|
||||
Function(FilePickerStatus)? onFileLoading,
|
||||
bool? allowCompression = true,
|
||||
bool allowMultiple = false,
|
||||
|
@ -48,7 +47,7 @@ class FilePickerIO extends FilePicker {
|
|||
_channel.invokeMethod<bool>('clear');
|
||||
|
||||
@override
|
||||
Future<String?> getDirectoryPath() async {
|
||||
Future<String?> getDirectoryPath({String? dialogTitle}) async {
|
||||
try {
|
||||
return await _channel.invokeMethod('dir', {});
|
||||
} on PlatformException catch (ex) {
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
import 'dart:async';
|
||||
import 'package:file_picker/src/file_picker.dart';
|
||||
import 'package:file_picker/src/file_picker_result.dart';
|
||||
import 'package:file_picker/src/platform_file.dart';
|
||||
import 'package:file_picker/src/utils.dart';
|
||||
|
||||
class FilePickerLinux extends FilePicker {
|
||||
@override
|
||||
Future<FilePickerResult?> pickFiles({
|
||||
String? dialogTitle,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
Function(FilePickerStatus)? onFileLoading,
|
||||
bool allowCompression = true,
|
||||
bool allowMultiple = false,
|
||||
bool withData = false,
|
||||
bool withReadStream = false,
|
||||
}) async {
|
||||
final String executable = await _getPathToExecutable();
|
||||
final String fileFilter = fileTypeToFileFilter(
|
||||
type,
|
||||
allowedExtensions,
|
||||
);
|
||||
final List<String> arguments = generateCommandLineArguments(
|
||||
dialogTitle ?? defaultDialogTitle,
|
||||
fileFilter: fileFilter,
|
||||
multipleFiles: allowMultiple,
|
||||
pickDirectory: false,
|
||||
);
|
||||
|
||||
final String? fileSelectionResult = await runExecutableWithArguments(
|
||||
executable,
|
||||
arguments,
|
||||
);
|
||||
if (fileSelectionResult == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<String> filePaths = resultStringToFilePaths(
|
||||
fileSelectionResult,
|
||||
);
|
||||
final List<PlatformFile> platformFiles = await filePathsToPlatformFiles(
|
||||
filePaths,
|
||||
withReadStream,
|
||||
withData,
|
||||
);
|
||||
|
||||
return FilePickerResult(platformFiles);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getDirectoryPath({
|
||||
String? dialogTitle,
|
||||
}) async {
|
||||
final executable = await _getPathToExecutable();
|
||||
final arguments = generateCommandLineArguments(
|
||||
dialogTitle ?? defaultDialogTitle,
|
||||
pickDirectory: true,
|
||||
);
|
||||
return await runExecutableWithArguments(executable, arguments);
|
||||
}
|
||||
|
||||
/// Returns the path to the executables `qarma` or `zenity` as a [String].
|
||||
///
|
||||
/// On Linux, the CLI tools `qarma` or `zenity` can be used to open a native
|
||||
/// file picker dialog. It seems as if all Linux distributions have at least
|
||||
/// one of these two tools pre-installed (on Ubuntu `zenity` is pre-installed).
|
||||
/// The future returns an error, if neither of both executables was found on
|
||||
/// the path.
|
||||
Future<String> _getPathToExecutable() async {
|
||||
try {
|
||||
return await isExecutableOnPath('qarma');
|
||||
} on Exception {
|
||||
return await isExecutableOnPath('zenity');
|
||||
}
|
||||
}
|
||||
|
||||
String fileTypeToFileFilter(FileType type, List<String>? allowedExtensions) {
|
||||
switch (type) {
|
||||
case FileType.any:
|
||||
return '*.*';
|
||||
case FileType.audio:
|
||||
return '*.mp3 *.wav *.midi *.ogg *.aac';
|
||||
case FileType.custom:
|
||||
return '*.' + allowedExtensions!.join(' *.');
|
||||
case FileType.image:
|
||||
return '*.bmp *.gif *.jpg *.jpeg *.png';
|
||||
case FileType.media:
|
||||
return '*.webm *.mpeg *.mkv *.mp4 *.avi *.mov *.flv *.jpg *.jpeg *.bmp *.gif *.png';
|
||||
case FileType.video:
|
||||
return '*.webm *.mpeg *.mkv *.mp4 *.avi *.mov *.flv';
|
||||
default:
|
||||
throw Exception('unknown file type');
|
||||
}
|
||||
}
|
||||
|
||||
List<String> generateCommandLineArguments(
|
||||
String dialogTitle, {
|
||||
String fileFilter = '',
|
||||
bool multipleFiles = false,
|
||||
bool pickDirectory = false,
|
||||
}) {
|
||||
final arguments = ['--file-selection', '--title', dialogTitle];
|
||||
|
||||
if (fileFilter.isNotEmpty) {
|
||||
arguments.add('--file-filter=$fileFilter');
|
||||
}
|
||||
|
||||
if (multipleFiles) {
|
||||
arguments.add('--multiple');
|
||||
}
|
||||
|
||||
if (pickDirectory) {
|
||||
arguments.add('--directory');
|
||||
}
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
/// Transforms the result string (stdout) of `qarma` / `zenity` into a [List]
|
||||
/// of file paths.
|
||||
List<String> resultStringToFilePaths(String fileSelectionResult) {
|
||||
if (fileSelectionResult.trim().isEmpty) {
|
||||
return [];
|
||||
}
|
||||
return fileSelectionResult.split('|');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:file_picker/src/utils.dart';
|
||||
|
||||
class FilePickerMacOS extends FilePicker {
|
||||
@override
|
||||
Future<FilePickerResult?> pickFiles({
|
||||
String? dialogTitle,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
Function(FilePickerStatus)? onFileLoading,
|
||||
bool allowCompression = true,
|
||||
bool allowMultiple = false,
|
||||
bool withData = false,
|
||||
bool withReadStream = false,
|
||||
}) async {
|
||||
final String executable = await isExecutableOnPath('osascript');
|
||||
final String fileFilter = fileTypeToFileFilter(
|
||||
type,
|
||||
allowedExtensions,
|
||||
);
|
||||
final List<String> arguments = generateCommandLineArguments(
|
||||
escapeDialogTitle(dialogTitle ?? defaultDialogTitle),
|
||||
fileFilter: fileFilter,
|
||||
multipleFiles: allowMultiple,
|
||||
pickDirectory: false,
|
||||
);
|
||||
|
||||
final String? fileSelectionResult = await runExecutableWithArguments(
|
||||
executable,
|
||||
arguments,
|
||||
);
|
||||
if (fileSelectionResult == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<String> filePaths = resultStringToFilePaths(
|
||||
fileSelectionResult,
|
||||
);
|
||||
final List<PlatformFile> platformFiles = await filePathsToPlatformFiles(
|
||||
filePaths,
|
||||
withReadStream,
|
||||
withData,
|
||||
);
|
||||
|
||||
return FilePickerResult(platformFiles);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getDirectoryPath({
|
||||
String? dialogTitle,
|
||||
}) async {
|
||||
final String executable = await isExecutableOnPath('osascript');
|
||||
final List<String> arguments = generateCommandLineArguments(
|
||||
escapeDialogTitle(dialogTitle ?? defaultDialogTitle),
|
||||
pickDirectory: true,
|
||||
);
|
||||
|
||||
final String? directorySelectionResult = await runExecutableWithArguments(
|
||||
executable,
|
||||
arguments,
|
||||
);
|
||||
if (directorySelectionResult == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultStringToFilePaths(directorySelectionResult).first;
|
||||
}
|
||||
|
||||
String fileTypeToFileFilter(FileType type, List<String>? allowedExtensions) {
|
||||
switch (type) {
|
||||
case FileType.any:
|
||||
return '';
|
||||
case FileType.audio:
|
||||
return '"", "mp3", "wav", "midi", "ogg", "aac"';
|
||||
case FileType.custom:
|
||||
return '"", "' + allowedExtensions!.join('", "') + '"';
|
||||
case FileType.image:
|
||||
return '"", "jpg", "jpeg", "bmp", "gif", "png"';
|
||||
case FileType.media:
|
||||
return '"", "webm", "mpeg", "mkv", "mp4", "avi", "mov", "flv", "jpg", "jpeg", "bmp", "gif", "png"';
|
||||
case FileType.video:
|
||||
return '"", "webm", "mpeg", "mkv", "mp4", "avi", "mov", "flv"';
|
||||
default:
|
||||
throw Exception('unknown file type');
|
||||
}
|
||||
}
|
||||
|
||||
List<String> generateCommandLineArguments(
|
||||
String dialogTitle, {
|
||||
String fileFilter = '',
|
||||
bool multipleFiles = false,
|
||||
bool pickDirectory = false,
|
||||
}) {
|
||||
final arguments = ['-e'];
|
||||
|
||||
String argument = 'choose ';
|
||||
if (pickDirectory) {
|
||||
argument += 'folder ';
|
||||
} else {
|
||||
argument += 'file of type {$fileFilter} ';
|
||||
|
||||
if (multipleFiles) {
|
||||
argument += 'with multiple selections allowed ';
|
||||
}
|
||||
}
|
||||
|
||||
argument += 'with prompt "$dialogTitle"';
|
||||
arguments.add(argument);
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
String escapeDialogTitle(String dialogTitle) => dialogTitle
|
||||
.replaceAll('\\', '\\\\')
|
||||
.replaceAll('"', '\\"')
|
||||
.replaceAll('\n', '\\\n');
|
||||
|
||||
/// Transforms the result string (stdout) of `osascript` into a [List] of
|
||||
/// file paths.
|
||||
List<String> resultStringToFilePaths(String fileSelectionResult) {
|
||||
if (fileSelectionResult.trim().isEmpty) {
|
||||
return [];
|
||||
}
|
||||
return fileSelectionResult
|
||||
.trim()
|
||||
.split(', ')
|
||||
.map((String path) => path.trim())
|
||||
.where((String path) => path.isNotEmpty)
|
||||
.map((String path) {
|
||||
final pathElements = path.split(':').where((e) => e.isNotEmpty).toList();
|
||||
final alias = pathElements[0];
|
||||
|
||||
if (alias == 'alias macOS') {
|
||||
return '/' + pathElements.sublist(1).join('/');
|
||||
}
|
||||
|
||||
final volumeName = alias.substring(6);
|
||||
return ['/Volumes', volumeName, ...pathElements.sublist(1)].join('/');
|
||||
}).toList();
|
||||
}
|
||||
}
|
|
@ -5,9 +5,6 @@ import 'dart:typed_data';
|
|||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
import '../src/file_picker_result.dart';
|
||||
import '../src/platform_file.dart';
|
||||
|
||||
class FilePickerWeb extends FilePicker {
|
||||
late Element _target;
|
||||
final String _kFilePickerInputsDomId = '__file_picker_web-file-input';
|
||||
|
@ -39,6 +36,7 @@ class FilePickerWeb extends FilePicker {
|
|||
|
||||
@override
|
||||
Future<FilePickerResult?> pickFiles({
|
||||
String? dialogTitle,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
bool allowMultiple = false,
|
||||
|
@ -62,6 +60,11 @@ class FilePickerWeb extends FilePicker {
|
|||
uploadInput.accept = accept;
|
||||
|
||||
bool changeEventTriggered = false;
|
||||
|
||||
if (onFileLoading != null) {
|
||||
onFileLoading(FilePickerStatus.picking);
|
||||
}
|
||||
|
||||
void changeEventListener(e) {
|
||||
if (changeEventTriggered) {
|
||||
return;
|
||||
|
@ -86,6 +89,9 @@ class FilePickerWeb extends FilePicker {
|
|||
));
|
||||
|
||||
if (pickedFiles.length >= files.length) {
|
||||
if (onFileLoading != null) {
|
||||
onFileLoading(FilePickerStatus.done);
|
||||
}
|
||||
filesCompleter.complete(pickedFiles);
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +144,7 @@ class FilePickerWeb extends FilePicker {
|
|||
_target.children.add(uploadInput);
|
||||
uploadInput.click();
|
||||
|
||||
final files = await filesCompleter.future;
|
||||
final List<PlatformFile>? files = await filesCompleter.future;
|
||||
|
||||
return files == null ? null : FilePickerResult(files);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
Future<List<PlatformFile>> filePathsToPlatformFiles(
|
||||
List<String> filePaths,
|
||||
bool withReadStream,
|
||||
bool withData,
|
||||
) {
|
||||
return Future.wait(
|
||||
filePaths
|
||||
.where((String filePath) => filePath.isNotEmpty)
|
||||
.map((String filePath) async {
|
||||
final file = File(filePath);
|
||||
|
||||
if (withReadStream) {
|
||||
return createPlatformFile(file, null, file.openRead());
|
||||
}
|
||||
|
||||
if (!withData) {
|
||||
return createPlatformFile(file, null, null);
|
||||
}
|
||||
|
||||
final bytes = await file.readAsBytes();
|
||||
return createPlatformFile(file, bytes, null);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<PlatformFile> createPlatformFile(
|
||||
File file,
|
||||
Uint8List? bytes,
|
||||
Stream<List<int>>? readStream,
|
||||
) async =>
|
||||
PlatformFile(
|
||||
bytes: bytes,
|
||||
name: basename(file.path),
|
||||
path: file.path,
|
||||
readStream: readStream,
|
||||
size: await file.length(),
|
||||
);
|
||||
|
||||
Future<String?> runExecutableWithArguments(
|
||||
String executable,
|
||||
List<String> arguments,
|
||||
) async {
|
||||
final processResult = await Process.run(executable, arguments);
|
||||
final path = processResult.stdout?.toString().trim();
|
||||
if (processResult.exitCode != 0 || path == null || path.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<String> isExecutableOnPath(String executable) async {
|
||||
final path = await runExecutableWithArguments('which', [executable]);
|
||||
if (path == null) {
|
||||
throw Exception(
|
||||
'Couldn\'t find the executable $executable in the path.',
|
||||
);
|
||||
}
|
||||
return path;
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:file_picker/src/utils.dart';
|
||||
import 'package:file_picker/src/windows/file_picker_windows_ffi_types.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
FilePicker filePickerWithFFI() => FilePickerWindows();
|
||||
|
||||
class FilePickerWindows extends FilePicker {
|
||||
@override
|
||||
Future<FilePickerResult?> pickFiles({
|
||||
String? dialogTitle,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
Function(FilePickerStatus)? onFileLoading,
|
||||
bool allowCompression = true,
|
||||
bool allowMultiple = false,
|
||||
bool withData = false,
|
||||
bool withReadStream = false,
|
||||
}) async {
|
||||
final comdlg32 = DynamicLibrary.open('comdlg32.dll');
|
||||
|
||||
final getOpenFileNameW =
|
||||
comdlg32.lookupFunction<GetOpenFileNameW, GetOpenFileNameWDart>(
|
||||
'GetOpenFileNameW');
|
||||
|
||||
final Pointer<OPENFILENAMEW> openFileName = calloc<OPENFILENAMEW>();
|
||||
openFileName.ref.lStructSize = sizeOf<OPENFILENAMEW>();
|
||||
openFileName.ref.lpstrTitle =
|
||||
(dialogTitle ?? defaultDialogTitle).toNativeUtf16();
|
||||
openFileName.ref.lpstrFile = calloc.allocate<Utf16>(maxPath);
|
||||
openFileName.ref.lpstrFilter =
|
||||
fileTypeToFileFilter(type, allowedExtensions).toNativeUtf16();
|
||||
openFileName.ref.nMaxFile = maxPath;
|
||||
openFileName.ref.lpstrInitialDir = ''.toNativeUtf16();
|
||||
openFileName.ref.flags = ofnExplorer | ofnFileMustExist | ofnHideReadOnly;
|
||||
if (allowMultiple) {
|
||||
openFileName.ref.flags |= ofnAllowMultiSelect;
|
||||
}
|
||||
|
||||
final result = getOpenFileNameW(openFileName);
|
||||
if (result == 1) {
|
||||
final filePaths =
|
||||
_extractSelectedFilesFromOpenFileNameW(openFileName.ref);
|
||||
final platformFiles =
|
||||
await filePathsToPlatformFiles(filePaths, withReadStream, withData);
|
||||
|
||||
return FilePickerResult(platformFiles);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getDirectoryPath({
|
||||
String? dialogTitle,
|
||||
}) {
|
||||
final pathIdPointer = _pickDirectory(dialogTitle ?? defaultDialogTitle);
|
||||
if (pathIdPointer == null) {
|
||||
return Future.value(null);
|
||||
}
|
||||
return Future.value(
|
||||
_getPathFromItemIdentifierList(pathIdPointer),
|
||||
);
|
||||
}
|
||||
|
||||
String fileTypeToFileFilter(FileType type, List<String>? allowedExtensions) {
|
||||
switch (type) {
|
||||
case FileType.any:
|
||||
return '*.*\x00\x00';
|
||||
case FileType.audio:
|
||||
return 'Audios (*.mp3)\x00*.mp3\x00All Files (*.*)\x00*.*\x00\x00';
|
||||
case FileType.custom:
|
||||
return 'Files (*.${allowedExtensions!.join(',*.')})\x00\x00';
|
||||
case FileType.image:
|
||||
return 'Images (*.jpeg,*.png,*.gif)\x00*.jpg;*.jpeg;*.png;*.gif\x00All Files (*.*)\x00*.*\x00\x00';
|
||||
case FileType.media:
|
||||
return 'Videos (*.webm,*.wmv,*.mpeg,*.mkv,*.mp4,*.avi,*.mov,*.flv)\x00*.webm;*.wmv;*.mpeg;*.mkv;*mp4;*.avi;*.mov;*.flv\x00Images (*.jpeg,*.png,*.gif)\x00*.jpg;*.jpeg;*.png;*.gif\x00All Files (*.*)\x00*.*\x00\x00';
|
||||
case FileType.video:
|
||||
return 'Videos (*.webm,*.wmv,*.mpeg,*.mkv,*.mp4,*.avi,*.mov,*.flv)\x00*.webm;*.wmv;*.mpeg;*.mkv;*mp4;*.avi;*.mov;*.flv\x00All Files (*.*)\x00*.*\x00\x00';
|
||||
default:
|
||||
throw Exception('unknown file type');
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses the Win32 API to display a dialog box that enables the user to select a folder.
|
||||
///
|
||||
/// Returns a PIDL that specifies the location of the selected folder relative to the root of the
|
||||
/// namespace. Returns null, if the user clicked on the "Cancel" button in the dialog box.
|
||||
Pointer? _pickDirectory(String dialogTitle) {
|
||||
final shell32 = DynamicLibrary.open('shell32.dll');
|
||||
|
||||
final shBrowseForFolderW =
|
||||
shell32.lookupFunction<SHBrowseForFolderW, SHBrowseForFolderW>(
|
||||
'SHBrowseForFolderW');
|
||||
|
||||
final Pointer<BROWSEINFOA> browseInfo = calloc<BROWSEINFOA>();
|
||||
browseInfo.ref.hwndOwner = nullptr;
|
||||
browseInfo.ref.pidlRoot = nullptr;
|
||||
browseInfo.ref.pszDisplayName = calloc.allocate<Utf16>(maxPath);
|
||||
browseInfo.ref.lpszTitle = dialogTitle.toNativeUtf16();
|
||||
browseInfo.ref.ulFlags =
|
||||
bifEditBox | bifNewDialogStyle | bifReturnOnlyFsDirs;
|
||||
|
||||
final Pointer<NativeType> itemIdentifierList =
|
||||
shBrowseForFolderW(browseInfo);
|
||||
|
||||
calloc.free(browseInfo.ref.pszDisplayName);
|
||||
calloc.free(browseInfo.ref.lpszTitle);
|
||||
calloc.free(browseInfo);
|
||||
|
||||
if (itemIdentifierList == nullptr) {
|
||||
return null;
|
||||
}
|
||||
return itemIdentifierList;
|
||||
}
|
||||
|
||||
/// Uses the Win32 API to convert an item identifier list to a file system path.
|
||||
///
|
||||
/// [lpItem] must contain the address of an item identifier list that specifies a
|
||||
/// file or directory location relative to the root of the namespace (the desktop).
|
||||
/// Returns the file system path as a [String]. Throws an exception, if the
|
||||
/// conversion wasn't successful.
|
||||
String _getPathFromItemIdentifierList(Pointer lpItem) {
|
||||
final shell32 = DynamicLibrary.open('shell32.dll');
|
||||
|
||||
final shGetPathFromIDListW =
|
||||
shell32.lookupFunction<SHGetPathFromIDListW, SHGetPathFromIDListWDart>(
|
||||
'SHGetPathFromIDListW');
|
||||
|
||||
final Pointer<Utf16> pszPath = calloc.allocate<Utf16>(maxPath);
|
||||
|
||||
final int result = shGetPathFromIDListW(lpItem, pszPath);
|
||||
if (result == 0x00000000) {
|
||||
throw Exception(
|
||||
'Failed to convert item identifier list to a file system path.');
|
||||
}
|
||||
|
||||
calloc.free(pszPath);
|
||||
|
||||
return pszPath.toDartString();
|
||||
}
|
||||
|
||||
/// Extracts the list of selected files from the Win32 API struct [OPENFILENAMEW].
|
||||
///
|
||||
/// After the user has closed the file picker dialog, Win32 API sets the property
|
||||
/// [lpstrFile] of [OPENFILENAMEW] to the user's selection. This property contains
|
||||
/// a string terminated by two [null] characters. If the user has selected only one
|
||||
/// file, then the returned string contains the absolute file path, e. g.
|
||||
/// `C:\Users\John\file1.jpg\x00\x00`. If the user has selected more than one file,
|
||||
/// then the returned string contains the directory of the selected files, followed
|
||||
/// by a [null] character, followed by the file names each separated by a [null]
|
||||
/// character, e.g. `C:\Users\John\x00file1.jpg\x00file2.jpg\x00file3.jpg\x00\x00`.
|
||||
List<String> _extractSelectedFilesFromOpenFileNameW(
|
||||
OPENFILENAMEW openFileNameW,
|
||||
) {
|
||||
final List<String> filePaths = [];
|
||||
final buffer = StringBuffer();
|
||||
int i = 0;
|
||||
bool lastCharWasNull = false;
|
||||
|
||||
while (true) {
|
||||
final char = openFileNameW.lpstrFile.cast<Uint16>().elementAt(i).value;
|
||||
if (char == 0) {
|
||||
if (lastCharWasNull) {
|
||||
break;
|
||||
} else {
|
||||
filePaths.add(buffer.toString());
|
||||
buffer.clear();
|
||||
lastCharWasNull = true;
|
||||
}
|
||||
} else {
|
||||
lastCharWasNull = false;
|
||||
buffer.writeCharCode(char);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (filePaths.length > 1) {
|
||||
final String directoryPath = filePaths.removeAt(0);
|
||||
return filePaths
|
||||
.map<String>((filePath) => join(directoryPath, filePath))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
/// Function from Win32 API to display a dialog box that enables the user to select a Shell folder.
|
||||
///
|
||||
/// Reference:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shbrowseforfolderw
|
||||
typedef SHBrowseForFolderW = Pointer Function(
|
||||
/// A pointer to a [BROWSEINFOA] structure that contains information used to display the dialog box.
|
||||
Pointer lpbi,
|
||||
);
|
||||
|
||||
/// Function from Win32 API to create an Open dialog box that lets the user specify the drive,
|
||||
/// directory, and the name of a file or set of files to be opened.
|
||||
///
|
||||
/// Reference:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nf-commdlg-getopenfilenamew
|
||||
typedef GetOpenFileNameW = Int8 Function(
|
||||
/// A pointer to an [OPENFILENAMEW] structure that contains information used to initialize the
|
||||
/// dialog box. When GetOpenFileName returns, this structure contains information about the user's
|
||||
/// file selection.
|
||||
Pointer unnamedParam1,
|
||||
);
|
||||
|
||||
/// Dart equivalent of [GetOpenFileNameW].
|
||||
typedef GetOpenFileNameWDart = int Function(
|
||||
Pointer unnamedParam1,
|
||||
);
|
||||
|
||||
/// Function from Win32 API to convert an item identifier list to a file system path.
|
||||
///
|
||||
/// Returns [true] if successful; otherwise, [false].
|
||||
///
|
||||
/// Reference:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetpathfromidlistw
|
||||
typedef SHGetPathFromIDListW = Int8 Function(
|
||||
/// The address of an item identifier list that specifies a file or directory location relative to
|
||||
/// the root of the namespace (the desktop).
|
||||
Pointer pidl,
|
||||
|
||||
/// The address of a buffer to receive the file system path. This buffer must be at least [maxPath]
|
||||
/// characters in size.
|
||||
Pointer<Utf16> pszPath,
|
||||
);
|
||||
|
||||
/// Dart equivalent of [SHGetPathFromIDListW].
|
||||
typedef SHGetPathFromIDListWDart = int Function(
|
||||
Pointer pidl,
|
||||
Pointer<Utf16> pszPath,
|
||||
);
|
||||
|
||||
/// Struct from Win32 API that contains parameters for the [SHBrowseForFolderW] function and receives
|
||||
/// information about the folder selected by the user.
|
||||
///
|
||||
/// Reference:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfoa
|
||||
class BROWSEINFOA extends Struct {
|
||||
/// A handle to the owner window for the dialog box.
|
||||
external Pointer hwndOwner;
|
||||
|
||||
/// A PIDL that specifies the location of the root folder from which to start browsing. Only the
|
||||
/// specified folder and its subfolders in the namespace hierarchy appear in the dialog box. This
|
||||
/// member can be [null]; in that case, a default location is used.
|
||||
external Pointer pidlRoot;
|
||||
|
||||
/// Pointer to a buffer to receive the display name of the folder selected by the user. The size
|
||||
/// of this buffer is assumed to be [maxPath] characters.
|
||||
external Pointer<Utf16> pszDisplayName;
|
||||
|
||||
/// Pointer to a null-terminated string that is displayed above the tree view control in the dialog
|
||||
/// box. This string can be used to specify instructions to the user.
|
||||
external Pointer lpszTitle;
|
||||
|
||||
/// Flags that specify the options for the dialog box. This member can be 0 or a combination of the
|
||||
/// following values.
|
||||
@Uint32()
|
||||
external int ulFlags;
|
||||
|
||||
/// Pointer to an application-defined function that the dialog box calls when an event occurs. For
|
||||
/// more information, see the BrowseCallbackProc function. This member can be [null].
|
||||
external Pointer lpfn;
|
||||
|
||||
/// An application-defined value that the dialog box passes to the callback function, if one is
|
||||
/// specified in [lpfn].
|
||||
external Pointer lParam;
|
||||
|
||||
/// An [int] value that receives the index of the image associated with the selected folder, stored
|
||||
/// in the system image list.
|
||||
@Uint32()
|
||||
external int iImage;
|
||||
}
|
||||
|
||||
/// Struct from Win32 API that contains parameters for the [GetOpenFileNameW] function and receives
|
||||
/// information about the file(s) selected by the user.
|
||||
///
|
||||
/// Reference:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew
|
||||
class OPENFILENAMEW extends Struct {
|
||||
/// The length, in bytes, of the structure. Use sizeof [OPENFILENAMEW] for this parameter.
|
||||
@Uint32()
|
||||
external int lStructSize;
|
||||
|
||||
/// A handle to the window that owns the dialog box. This member can be any valid window handle, or it can be [null] if the dialog box has no owner.
|
||||
external Pointer hwndOwner;
|
||||
|
||||
/// If the OFN_ENABLETEMPLATEHANDLE flag is set in the Flags member, hInstance is a handle to a memory object containing a dialog box template. If the OFN_ENABLETEMPLATE flag is set, hInstance is a handle to a module that contains a dialog box template named by the lpTemplateName member. If neither flag is set, this member is ignored. If the OFN_EXPLORER flag is set, the system uses the specified template to create a dialog box that is a child of the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses the template to create an old-style dialog box that replaces the default dialog box.
|
||||
external Pointer hInstance;
|
||||
|
||||
/// A buffer containing pairs of null-terminated filter strings. The last string in the buffer must be terminated by two [null] characters.
|
||||
external Pointer<Utf16> lpstrFilter;
|
||||
|
||||
/// A static buffer that contains a pair of null-terminated filter strings for preserving the filter pattern chosen by the user.
|
||||
external Pointer<Utf16> lpstrCustomFilter;
|
||||
|
||||
/// The size, in characters, of the buffer identified by [lpstrCustomFilter]. This buffer should be at least 40 characters long. This member is ignored if [lpstrCustomFilter] is [null] or points to a [null] string.
|
||||
@Uint32()
|
||||
external int nMaxCustFilter;
|
||||
|
||||
/// The index of the currently selected filter in the File Types control.
|
||||
@Uint32()
|
||||
external int nFilterIndex;
|
||||
|
||||
/// The file name used to initialize the File Name edit control. The first character of this buffer must be [null] if initialization is not necessary.
|
||||
external Pointer<Utf16> lpstrFile;
|
||||
|
||||
/// The size, in characters, of the buffer pointed to by lpstrFile. The buffer must be large enough to store the path and file name string or strings, including the terminating [null] character. The GetOpenFileName and GetSaveFileName functions return [false] if the buffer is too small to contain the file information. The buffer should be at least 256 characters long.
|
||||
@Uint32()
|
||||
external int nMaxFile;
|
||||
|
||||
/// The file name and extension (without path information) of the selected file. This member can be [null].
|
||||
external Pointer<Utf16> lpstrFileTitle;
|
||||
|
||||
/// The size, in characters, of the buffer pointed to by [lpstrFileTitle]. This member is ignored if [lpstrFileTitle] is [null].
|
||||
@Uint32()
|
||||
external int nMaxFileTitle;
|
||||
|
||||
/// The initial directory. The algorithm for selecting the initial directory varies on different platforms.
|
||||
external Pointer<Utf16> lpstrInitialDir;
|
||||
|
||||
/// A string to be placed in the title bar of the dialog box. If this member is [null], the system uses the default title (that is, Save As or Open).
|
||||
external Pointer<Utf16> lpstrTitle;
|
||||
|
||||
/// A set of bit flags you can use to initialize the dialog box. When the dialog box returns, it sets these flags to indicate the user's input.
|
||||
@Uint32()
|
||||
external int flags;
|
||||
|
||||
/// The zero-based offset, in characters, from the beginning of the path to the file name in the string pointed to by [lpstrFile].
|
||||
@Uint16()
|
||||
external int nFileOffset;
|
||||
|
||||
/// The zero-based offset, in characters, from the beginning of the path to the file name extension in the string pointed to by [lpstrFile].
|
||||
@Uint16()
|
||||
external int nFileExtension;
|
||||
|
||||
/// The default extension. GetOpenFileName and GetSaveFileName append this extension to the file name if the user fails to type an extension.
|
||||
external Pointer<Utf16> lpstrDefExt;
|
||||
|
||||
/// Application-defined data that the system passes to the hook procedure identified by the lpfnHook member. When the system sends the WM_INITDIALOG message to the hook procedure, the message's lParam parameter is a pointer to the OPENFILENAME structure specified when the dialog box was created. The hook procedure can use this pointer to get the lCustData value.
|
||||
external Pointer lCustData;
|
||||
|
||||
/// A pointer to a hook procedure. This member is ignored unless the Flags member includes the OFN_ENABLEHOOK flag.
|
||||
external Pointer lpfnHook;
|
||||
|
||||
/// The name of the dialog template resource in the module identified by the hInstance member. For numbered dialog box resources, this can be a value returned by the MAKEINTRESOURCE macro. This member is ignored unless the OFN_ENABLETEMPLATE flag is set in the Flags member. If the OFN_EXPLORER flag is set, the system uses the specified template to create a dialog box that is a child of the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses the template to create an old-style dialog box that replaces the default dialog box.
|
||||
external Pointer<Utf16> lpTemplateName;
|
||||
|
||||
/// This member is reserved.
|
||||
external Pointer pvReserved;
|
||||
|
||||
/// This member is reserved.
|
||||
@Uint32()
|
||||
external int dwReserved;
|
||||
|
||||
/// A set of bit flags you can use to initialize the dialog box.
|
||||
@Uint32()
|
||||
external int flagsEx;
|
||||
}
|
||||
|
||||
/// Only return file system directories. If the user selects folders that are not part of the file
|
||||
/// system, the OK button is grayed.
|
||||
const bifReturnOnlyFsDirs = 0x00000001;
|
||||
|
||||
/// Include an edit control in the browse dialog box that allows the user to type the name of an item.
|
||||
const bifEditBox = 0x00000010;
|
||||
|
||||
/// Use the new user interface. Setting this flag provides the user with a larger dialog box that can
|
||||
/// be resized. The dialog box has several new capabilities, including: drag-and-drop capability within
|
||||
/// the dialog box, reordering, shortcut menus, new folders, delete, and other shortcut menu commands.
|
||||
const bifNewDialogStyle = 0x00000040;
|
||||
|
||||
/// In the Windows API, the maximum length for a path is MAX_PATH, which is defined as 260 characters.
|
||||
const maxPath = 260;
|
||||
|
||||
/// The File Name list box allows multiple selections.
|
||||
const ofnAllowMultiSelect = 0x00000200;
|
||||
|
||||
/// Indicates that any customizations made to the Open or Save As dialog box use the Explorer-style customization methods.
|
||||
const ofnExplorer = 0x00080000;
|
||||
|
||||
/// The user can type only names of existing files in the File Name entry field.
|
||||
const ofnFileMustExist = 0x00001000;
|
||||
|
||||
/// Hides the Read Only check box.
|
||||
const ofnHideReadOnly = 0x00000004;
|
|
@ -0,0 +1,4 @@
|
|||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
/// Stub method to support both dart:ffi and web
|
||||
FilePicker filePickerWithFFI() => throw UnimplementedError('Unsupported');
|
14
pubspec.yaml
|
@ -1,7 +1,9 @@
|
|||
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: 3.0.4
|
||||
repository: https://github.com/miguelpruivo/flutter_file_picker
|
||||
issue_tracker: https://github.com/miguelpruivo/flutter_file_picker/issues
|
||||
version: 4.0.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
@ -11,6 +13,8 @@ dependencies:
|
|||
|
||||
flutter_plugin_android_lifecycle: ^2.0.0
|
||||
plugin_platform_interface: ^2.0.0
|
||||
ffi: ^1.1.2
|
||||
path: ^1.8.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
@ -26,4 +30,10 @@ flutter:
|
|||
pluginClass: FilePickerPlugin
|
||||
web:
|
||||
pluginClass: FilePickerWeb
|
||||
fileName: _internal/file_picker_web.dart
|
||||
fileName: src/file_picker_web.dart
|
||||
macos:
|
||||
default_package: file_picker
|
||||
windows:
|
||||
default_package: file_picker
|
||||
linux:
|
||||
default_package: file_picker
|