import 'package:file_picker/file_picker.dart'; import 'package:file_picker/src/utils.dart'; class FilePickerMacOS extends FilePicker { @override Future pickFiles({ String? dialogTitle, FileType type = FileType.any, List? 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 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 filePaths = resultStringToFilePaths( fileSelectionResult, ); final List platformFiles = await filePathsToPlatformFiles( filePaths, withReadStream, withData, ); return FilePickerResult(platformFiles); } @override Future getDirectoryPath({ String? dialogTitle, }) async { final String executable = await isExecutableOnPath('osascript'); final List 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? 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 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 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(); } }