diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index ee08d0510f3c..3180f9b1e763 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,6 +1,7 @@
-## NEXT
+## 1.1.3
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
+* Updates README to include a reference to the macOS PHPicker feature.
## 1.1.2
diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md
index 866adf58118e..ef91fa400236 100755
--- a/packages/image_picker/image_picker/README.md
+++ b/packages/image_picker/image_picker/README.md
@@ -153,7 +153,7 @@ encourage the community to build packages that implement
#### macOS installation
-Since the macOS implementation uses `file_selector`, you will need to
+Since the macOS implementation uses `file_selector` by default, you will need to
add a filesystem access
[entitlement](https://flutter.dev/to/macos-entitlements):
@@ -162,6 +162,17 @@ add a filesystem access
```
+This setup is still required when using the [macOS PHPicker](#macos-phpicker) on **macOS 12 and older versions**, since it's only supported on **macOS 13+** and will fallback to the `file_selector` implementation.
+
+#### macOS PHPicker
+
+To use the [macOS native image picker](https://developer.apple.com/documentation/photokit/phpickerviewcontroller) which is supported on **macOS 13 and newer versions**,
+refer to the [image_picker_macos PHPicker](https://pub.dev/packages/image_picker_macos#phpicker) section.
+
+* **on macOS 13 and newer versions**: If this feature is used, the
+filesystem access entitlement in the [macOS installation](#macos-installation) is not required.
+* **on macOS 12 and older versions**: This feature is unsupported and will fallback to `file_selector` implementation, the filesystem access entitlement in the [macOS installation](#macos-installation) is required.
+
### Example
diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml
index d2f49724c883..5830715fed5d 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image
library, and taking new pictures with the camera.
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 1.1.2
+version: 1.1.3
environment:
sdk: ^3.4.0
diff --git a/packages/image_picker/image_picker_macos/CHANGELOG.md b/packages/image_picker/image_picker_macos/CHANGELOG.md
index f1b3bd3f1597..0052b5477306 100644
--- a/packages/image_picker/image_picker_macos/CHANGELOG.md
+++ b/packages/image_picker/image_picker_macos/CHANGELOG.md
@@ -1,6 +1,7 @@
-## NEXT
+## 0.3.0
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
+* Adds macOS 13+ PHPicker functionality (optional and disabled by default).
## 0.2.1+1
diff --git a/packages/image_picker/image_picker_macos/README.md b/packages/image_picker/image_picker_macos/README.md
index 9aa87453532e..9faa8ca266d9 100644
--- a/packages/image_picker/image_picker_macos/README.md
+++ b/packages/image_picker/image_picker_macos/README.md
@@ -2,15 +2,44 @@
A macOS implementation of [`image_picker`][1].
+## PHPicker
+
+macOS 13.0 and newer versions supports native image picking via [PHPickerViewController][5].
+
+To use this feature, add the following code to your app before calling any `image_picker` APIs:
+
+
+```dart
+import 'package:image_picker_macos/image_picker_macos.dart';
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+// ···
+ final ImagePickerPlatform imagePickerImplementation =
+ ImagePickerPlatform.instance;
+ if (imagePickerImplementation is ImagePickerMacOS) {
+ imagePickerImplementation.useMacOSPHPicker = true;
+ }
+```
+
+This implementation depends on the photos in the [Photos for macOS App][6],
+if the user didn't open the app or import any photos to the app,
+they will see: `No photos` or `No Photos or Videos` message even if they
+have them as files on their desktop. The macOS Photos app supports importing images from an iOS device.
+
+> [!NOTE]
+> This feature is only supported on **macOS 13.0 and newer versions**, on older versions it will fallback to using [`file_selector`][3] if enabled.
+> By defaults it's disabled on all versions.
+
## Limitations
`ImageSource.camera` is not supported unless a `cameraDelegate` is set.
### pickImage()
-The arguments `maxWidth`, `maxHeight`, and `imageQuality` are not currently supported.
+The arguments `maxWidth`, `maxHeight`, `imageQuality`, and `limit` are only supported when using the [PHPicker](#phpicker) implementation; they are not available in the default [file_selector][5] implementation.
+
+The argument `requestFullMetadata` is unsupported on macOS.
### pickVideo()
-The argument `maxDuration` is not currently supported.
+The argument `maxDuration` is not supported even when using the [PHPicker](#phpicker) implementation.
## Usage
@@ -25,14 +54,18 @@ should add it to your `pubspec.yaml` as usual.
### Entitlements
-This package is currently implemented using [`file_selector`][3], so you will
-need to add a read-only file acces [entitlement][4]:
+This package’s default implementation relies on [file_selector][3],
+which requires the following read-only file access entitlement:
```xml
com.apple.security.files.user-selected.read-only
```
+If you're using the [PHPicker](#phpicker) and require at **least macOS 13** to run the app, this entitlement is not required.
+
[1]: https://pub.dev/packages/image_picker
[2]: https://flutter.dev/to/endorsed-federated-plugin
[3]: https://pub.dev/packages/file_selector
[4]: https://flutter.dev/to/macos-entitlements
+[5]: https://developer.apple.com/documentation/photokit/phpickerviewcontroller
+[6]: https://www.apple.com/in/macos/photos/
diff --git a/packages/image_picker/image_picker_macos/example/integration_test/image_picker_test.dart b/packages/image_picker/image_picker_macos/example/integration_test/image_picker_test.dart
new file mode 100644
index 000000000000..d34915ca4992
--- /dev/null
+++ b/packages/image_picker/image_picker_macos/example/integration_test/image_picker_test.dart
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:example/main.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:image_picker_macos/image_picker_macos.dart';
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:integration_test/integration_test.dart';
+
+ImagePickerMacOS get requireMacOSImplementation {
+ final ImagePickerPlatform imagePickerImplementation =
+ ImagePickerPlatform.instance;
+ if (imagePickerImplementation is! ImagePickerMacOS) {
+ fail('Expected the implementation to be $ImagePickerMacOS');
+ }
+ return imagePickerImplementation;
+}
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+ group('example', () {
+ testWidgets(
+ 'Pressing the PHPicker toggle button updates it correctly',
+ (WidgetTester tester) async {
+ final ImagePickerMacOS imagePickerImplementation =
+ requireMacOSImplementation;
+ expect(imagePickerImplementation.useMacOSPHPicker, false,
+ reason: 'The default is to not using PHPicker');
+
+ await tester.pumpWidget(const MyApp());
+ final Finder togglePHPickerFinder =
+ find.byTooltip('toggle macOS PHPPicker');
+ expect(togglePHPickerFinder, findsOneWidget);
+
+ await tester.tap(togglePHPickerFinder);
+ expect(imagePickerImplementation.useMacOSPHPicker, true,
+ reason: 'Pressing the toggle button should update it correctly');
+
+ await tester.tap(togglePHPickerFinder);
+ expect(imagePickerImplementation.useMacOSPHPicker, false,
+ reason: 'Pressing the toggle button should update it correctly');
+ },
+ );
+ });
+}
diff --git a/packages/image_picker/image_picker_macos/example/lib/main.dart b/packages/image_picker/image_picker_macos/example/lib/main.dart
index 8f4887095c13..76da5f6e7f4d 100644
--- a/packages/image_picker/image_picker_macos/example/lib/main.dart
+++ b/packages/image_picker/image_picker_macos/example/lib/main.dart
@@ -8,11 +8,22 @@ import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
+// #docregion phpicker-example
+import 'package:image_picker_macos/image_picker_macos.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+// #enddocregion phpicker-example
import 'package:mime/mime.dart';
import 'package:video_player/video_player.dart';
void main() {
+ // Set to use macOS PHPicker.
+ // #docregion phpicker-example
+ final ImagePickerPlatform imagePickerImplementation =
+ ImagePickerPlatform.instance;
+ if (imagePickerImplementation is ImagePickerMacOS) {
+ imagePickerImplementation.useMacOSPHPicker = true;
+ }
+ // #enddocregion phpicker-example
runApp(const MyApp());
}
@@ -385,6 +396,46 @@ class _MyHomePageState extends State {
child: const Icon(Icons.videocam),
),
),
+ Padding(
+ padding: const EdgeInsets.only(top: 16.0),
+ child: FloatingActionButton(
+ onPressed: () {
+ void showSnackbarText(String text) {
+ ScaffoldMessenger.of(context)
+ ..clearSnackBars()
+ ..showSnackBar(
+ SnackBar(content: Text(text)),
+ );
+ }
+
+ if (_picker is! ImagePickerMacOS) {
+ throw StateError(
+ 'Expected the implementation to be $ImagePickerMacOS but was ${_picker.runtimeType}');
+ }
+
+ if (_picker.useMacOSPHPicker) {
+ _picker.useMacOSPHPicker = false;
+ setState(() {});
+ showSnackbarText('Switched to file_picker implementation.');
+ } else {
+ _picker.useMacOSPHPicker = true;
+ setState(() {});
+ showSnackbarText(
+ 'Switched to macOS PHPPicker implementation.');
+ }
+ },
+ tooltip: 'toggle macOS PHPPicker',
+ child: () {
+ if (_picker is ImagePickerMacOS) {
+ return _picker.useMacOSPHPicker
+ ? const Icon(Icons.apple)
+ : const Icon(Icons.file_open);
+ }
+ throw StateError(
+ 'Expected the implementation to be $ImagePickerMacOS but was ${_picker.runtimeType}');
+ }(),
+ ),
+ ),
],
),
);
diff --git a/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/project.pbxproj b/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/project.pbxproj
index 69e3b8961bfa..06bf3a683ab6 100644
--- a/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/project.pbxproj
@@ -27,6 +27,11 @@
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
+ 7AA9FB252D27461200BBBB29 /* ImageTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA9FB242D27461000BBBB29 /* ImageTestUtils.swift */; };
+ 7ABC95832CBD9D810004CBA6 /* ImageCompressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABC957F2CBD9D810004CBA6 /* ImageCompressTests.swift */; };
+ 7ABC95842CBD9D810004CBA6 /* ImageResizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABC95802CBD9D810004CBA6 /* ImageResizeTests.swift */; };
+ 7ABC95852CBD9D810004CBA6 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABC95812CBD9D810004CBA6 /* RunnerTests.swift */; };
+ 7ABC95892CBD9D8A0004CBA6 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABC95862CBD9D8A0004CBA6 /* RunnerUITests.swift */; };
BBE2B8C47A32673657F9E2DC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B40E0F19CFF2111C7C9F07D /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
@@ -38,6 +43,20 @@
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
+ 7ABC952F2CB979800004CBA6 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC10EC2044A3C60003C045;
+ remoteInfo = Runner;
+ };
+ 7ABC954F2CBAF9680004CBA6 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC10EC2044A3C60003C045;
+ remoteInfo = Runner;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -69,6 +88,13 @@
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
450A6A13EFEFF8F54A1685E1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 7AA9FB242D27461000BBBB29 /* ImageTestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTestUtils.swift; sourceTree = ""; };
+ 7ABC952B2CB979800004CBA6 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7ABC95492CBAF9680004CBA6 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7ABC957F2CBD9D810004CBA6 /* ImageCompressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompressTests.swift; sourceTree = ""; };
+ 7ABC95802CBD9D810004CBA6 /* ImageResizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageResizeTests.swift; sourceTree = ""; };
+ 7ABC95812CBD9D810004CBA6 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 7ABC95862CBD9D8A0004CBA6 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
7B40E0F19CFF2111C7C9F07D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
@@ -86,6 +112,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7ABC95282CB979800004CBA6 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7ABC95462CBAF9680004CBA6 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -105,6 +145,8 @@
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
+ 7ABC95822CBD9D810004CBA6 /* RunnerTests */,
+ 7ABC95882CBD9D8A0004CBA6 /* RunnerUITests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
E0DBDFAB26ECD71761DCC07A /* Pods */,
@@ -115,6 +157,8 @@
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* example.app */,
+ 7ABC952B2CB979800004CBA6 /* RunnerTests.xctest */,
+ 7ABC95492CBAF9680004CBA6 /* RunnerUITests.xctest */,
);
name = Products;
sourceTree = "";
@@ -154,6 +198,25 @@
path = Runner;
sourceTree = "";
};
+ 7ABC95822CBD9D810004CBA6 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 7AA9FB242D27461000BBBB29 /* ImageTestUtils.swift */,
+ 7ABC957F2CBD9D810004CBA6 /* ImageCompressTests.swift */,
+ 7ABC95802CBD9D810004CBA6 /* ImageResizeTests.swift */,
+ 7ABC95812CBD9D810004CBA6 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 7ABC95882CBD9D8A0004CBA6 /* RunnerUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 7ABC95862CBD9D8A0004CBA6 /* RunnerUITests.swift */,
+ );
+ path = RunnerUITests;
+ sourceTree = "";
+ };
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -169,7 +232,6 @@
EA63AF049335E53A5E609A89 /* Pods-Runner.release.xcconfig */,
AFE3DE60B5E9D50D0C5C0524 /* Pods-Runner.profile.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "";
};
@@ -200,13 +262,49 @@
productReference = 33CC10ED2044A3C60003C045 /* example.app */;
productType = "com.apple.product-type.application";
};
+ 7ABC952A2CB979800004CBA6 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7ABC95342CB979800004CBA6 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 7ABC95272CB979800004CBA6 /* Sources */,
+ 7ABC95282CB979800004CBA6 /* Frameworks */,
+ 7ABC95292CB979800004CBA6 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 7ABC95302CB979800004CBA6 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 7ABC952B2CB979800004CBA6 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 7ABC95482CBAF9680004CBA6 /* RunnerUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7ABC95542CBAF9680004CBA6 /* Build configuration list for PBXNativeTarget "RunnerUITests" */;
+ buildPhases = (
+ 7ABC95452CBAF9680004CBA6 /* Sources */,
+ 7ABC95462CBAF9680004CBA6 /* Frameworks */,
+ 7ABC95472CBAF9680004CBA6 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 7ABC95502CBAF9680004CBA6 /* PBXTargetDependency */,
+ );
+ name = RunnerUITests;
+ productName = RunnerUITests;
+ productReference = 7ABC95492CBAF9680004CBA6 /* RunnerUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 0920;
+ LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
@@ -224,6 +322,15 @@
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
+ 7ABC952A2CB979800004CBA6 = {
+ CreatedOnToolsVersion = 16.0;
+ LastSwiftMigration = 1600;
+ TestTargetID = 33CC10EC2044A3C60003C045;
+ };
+ 7ABC95482CBAF9680004CBA6 = {
+ CreatedOnToolsVersion = 16.0;
+ TestTargetID = 33CC10EC2044A3C60003C045;
+ };
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
@@ -236,7 +343,7 @@
);
mainGroup = 33CC10E42044A3C60003C045;
packageReferences = (
- 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
@@ -244,6 +351,8 @@
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+ 7ABC952A2CB979800004CBA6 /* RunnerTests */,
+ 7ABC95482CBAF9680004CBA6 /* RunnerUITests */,
);
};
/* End PBXProject section */
@@ -258,6 +367,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7ABC95292CB979800004CBA6 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7ABC95472CBAF9680004CBA6 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -334,6 +457,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7ABC95272CB979800004CBA6 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7ABC95832CBD9D810004CBA6 /* ImageCompressTests.swift in Sources */,
+ 7ABC95842CBD9D810004CBA6 /* ImageResizeTests.swift in Sources */,
+ 7ABC95852CBD9D810004CBA6 /* RunnerTests.swift in Sources */,
+ 7AA9FB252D27461200BBBB29 /* ImageTestUtils.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7ABC95452CBAF9680004CBA6 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7ABC95892CBD9D8A0004CBA6 /* RunnerUITests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -342,6 +484,16 @@
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
+ 7ABC95302CB979800004CBA6 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC10EC2044A3C60003C045 /* Runner */;
+ targetProxy = 7ABC952F2CB979800004CBA6 /* PBXContainerItemProxy */;
+ };
+ 7ABC95502CBAF9680004CBA6 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC10EC2044A3C60003C045 /* Runner */;
+ targetProxy = 7ABC954F2CBAF9680004CBA6 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -584,6 +736,137 @@
};
name = Release;
};
+ 7ABC95312CB979800004CBA6 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example";
+ };
+ name = Debug;
+ };
+ 7ABC95322CB979800004CBA6 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example";
+ };
+ name = Release;
+ };
+ 7ABC95332CB979800004CBA6 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example";
+ };
+ name = Profile;
+ };
+ 7ABC95512CBAF9680004CBA6 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = Runner;
+ };
+ name = Debug;
+ };
+ 7ABC95522CBAF9680004CBA6 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = Runner;
+ };
+ name = Release;
+ };
+ 7ABC95532CBAF9680004CBA6 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = Runner;
+ };
+ name = Profile;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -617,10 +900,30 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 7ABC95342CB979800004CBA6 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7ABC95312CB979800004CBA6 /* Debug */,
+ 7ABC95322CB979800004CBA6 /* Release */,
+ 7ABC95332CB979800004CBA6 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7ABC95542CBAF9680004CBA6 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7ABC95512CBAF9680004CBA6 /* Debug */,
+ 7ABC95522CBAF9680004CBA6 /* Release */,
+ 7ABC95532CBAF9680004CBA6 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
- 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
diff --git a/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 8b5a3a2814eb..1d4cc11b9312 100644
--- a/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/image_picker/image_picker_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -55,6 +55,28 @@
+
+
+
+
+
+
+
+
NSImage {
+ let image = NSImage(size: size)
+ image.lockFocus()
+ NSColor.white.set()
+ NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill()
+ image.unlockFocus()
+ return image
+}
diff --git a/packages/image_picker/image_picker_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/image_picker/image_picker_macos/example/macos/RunnerTests/RunnerTests.swift
new file mode 100644
index 000000000000..6b6becdf3241
--- /dev/null
+++ b/packages/image_picker/image_picker_macos/example/macos/RunnerTests/RunnerTests.swift
@@ -0,0 +1,94 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import XCTest
+
+@testable import image_picker_macos
+
+final class RunnerTests: XCTestCase {
+
+ func testSupportsPHPicker() {
+ let imagePicker = ImagePickerImpl()
+ if #available(macOS 13.0, *) {
+ XCTAssertTrue(
+ imagePicker.supportsPHPicker(),
+ "PHPicker is expected to be supported on macOS 13.0 and newer versions.")
+ } else {
+ XCTAssertFalse(
+ imagePicker.supportsPHPicker(),
+ "PHPicker is expected to be unsupported on macOS versions older than 13.0.")
+ }
+ }
+
+ func testImageFileType() {
+ XCTAssertEqual(
+ imageFileType(quality: 100), NSBitmapImageRep.FileType.png,
+ "Quality 100 should return PNG file type.")
+ XCTAssertEqual(
+ imageFileType(quality: 99), NSBitmapImageRep.FileType.jpeg,
+ "Quality below 100 should return JPEG file type.")
+ XCTAssertEqual(
+ imageFileType(quality: nil), NSBitmapImageRep.FileType.png,
+ "Quality nil should return PNG file type.")
+ }
+
+ func testImageFileExt() {
+ XCTAssertEqual(
+ imageFileExt(fileType: NSBitmapImageRep.FileType.png), "png",
+ "File extension for PNG should be 'png'.")
+ XCTAssertEqual(
+ imageFileExt(fileType: NSBitmapImageRep.FileType.jpeg), "jpeg",
+ "File extension for JPEG should be 'jpeg'.")
+ }
+
+ func testGenerateUniqueImageFileName() {
+ let fileType = NSBitmapImageRep.FileType.jpeg
+ let generatedFileName = generateUniqueImageFileName(imageFileType: fileType)
+ let expectedExtension = imageFileExt(fileType: fileType)
+
+ // Extract the UUID part of the generated file name
+ let uuidStringFromFile = generatedFileName.replacingOccurrences(
+ of: ".\(expectedExtension)", with: "")
+
+ let fileUUID = UUID(uuidString: uuidStringFromFile)
+
+ XCTAssertNotNil(fileUUID, "Generated file name should start with a valid UUID.")
+ XCTAssertTrue(
+ generatedFileName.hasSuffix(".\(expectedExtension)"),
+ "Generated file name should have a '\(expectedExtension)' extension.")
+ }
+
+ func testGenerateTempImageFilePath() {
+ let fileType: NSBitmapImageRep.FileType = NSBitmapImageRep.FileType.png
+ let filePath = generateTempImageFilePath(imageFileType: fileType)
+ let fileExists = FileManager.default.fileExists(atPath: filePath.path)
+
+ XCTAssertFalse(fileExists, "The file at path \(filePath) should not exist.")
+ XCTAssertEqual(filePath.pathExtension, "png", "The file path should have a .png extension.")
+ XCTAssertTrue(
+ filePath.absoluteString.hasPrefix(FileManager.default.temporaryDirectory.absoluteString),
+ "The file path should be in the temporary directory.")
+
+ XCTAssertTrue(filePath.isFileURL, "The generated path should be a file URL.")
+
+ let anotherFilePath = generateTempImageFilePath(imageFileType: fileType)
+ XCTAssertNotEqual(filePath, anotherFilePath, "The generated file paths should be unique.")
+ }
+
+ func testPathString() {
+ let tempDirectory = FileManager.default.temporaryDirectory
+ let fileURL = tempDirectory.appendingPathComponent("flutter.dart")
+
+ XCTAssertEqual(
+ fileURL.path, fileURL.pathString(),
+ "Expected pathString() to match `URL.path` for the current URL.")
+
+ if #available(macOS 13.0, *) {
+ XCTAssertEqual(
+ fileURL.path(), fileURL.pathString(),
+ "Expected pathString() to match `URL.path()` for macOS 13.0 and later.")
+ }
+ }
+
+}
diff --git a/packages/image_picker/image_picker_macos/example/macos/RunnerUITests/RunnerUITests.swift b/packages/image_picker/image_picker_macos/example/macos/RunnerUITests/RunnerUITests.swift
new file mode 100644
index 000000000000..df50b4c08125
--- /dev/null
+++ b/packages/image_picker/image_picker_macos/example/macos/RunnerUITests/RunnerUITests.swift
@@ -0,0 +1,32 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import FlutterMacOS
+import XCTest
+
+@testable import image_picker_macos
+
+/// The specified amount of time for waiting to check if an element exists.
+let kElementWaitingTime: TimeInterval = 30
+
+final class RunnerUITests: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+ app = XCUIApplication()
+ app.launch()
+ }
+
+ override func tearDown() {
+ app.terminate()
+ }
+
+ @MainActor
+ func testImagePicker() throws {
+ // TODO(EchoEllet): Lacks native UI tests https://discord.com/channels/608014603317936148/1300517990957056080/1300518056690188361
+ // https://github.com/flutter/flutter/issues/70234
+ }
+}
diff --git a/packages/image_picker/image_picker_macos/example/pubspec.yaml b/packages/image_picker/image_picker_macos/example/pubspec.yaml
index 66435a80756b..c9b117318cf9 100644
--- a/packages/image_picker/image_picker_macos/example/pubspec.yaml
+++ b/packages/image_picker/image_picker_macos/example/pubspec.yaml
@@ -24,6 +24,8 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
+ integration_test:
+ sdk: flutter
flutter:
uses-material-design: true
diff --git a/packages/image_picker/image_picker_macos/example/test_driver/integration_test.dart b/packages/image_picker/image_picker_macos/example/test_driver/integration_test.dart
new file mode 100644
index 000000000000..4f10f2a522f3
--- /dev/null
+++ b/packages/image_picker/image_picker_macos/example/test_driver/integration_test.dart
@@ -0,0 +1,7 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:integration_test/integration_test_driver.dart';
+
+Future main() => integrationDriver();
diff --git a/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart b/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart
index 9e9447a5710c..41951c7bfe03 100644
--- a/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart
+++ b/packages/image_picker/image_picker_macos/lib/image_picker_macos.dart
@@ -5,8 +5,11 @@
import 'package:file_selector_macos/file_selector_macos.dart';
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'src/messages.g.dart';
+
/// The macOS implementation of [ImagePickerPlatform].
///
/// This class implements the `package:image_picker` functionality for
@@ -15,6 +18,10 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
/// Constructs a platform implementation.
ImagePickerMacOS();
+ /// The platform API generated by Pigeon to communicate with native macOS using a method channel.
+ /// Used only when [useMacOSPHPicker] is `true` for **PHPicker implementation**.
+ final ImagePickerApi _hostApi = ImagePickerApi();
+
/// The file selector used to prompt the user to select images or videos.
@visibleForTesting
static FileSelectorPlatform fileSelector = FileSelectorMacOS();
@@ -24,6 +31,39 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
ImagePickerPlatform.instance = ImagePickerMacOS();
}
+ /// Sets [ImagePickerMacOS] to use [PHPicker](https://developer.apple.com/documentation/photokit/phpickerviewcontroller)
+ /// which is **only supported on macOS 13.0+**.
+ ///
+ /// Will fallback to [file_selector_macos](https://pub.dev/packages/file_selector_macos)
+ /// if [useMacOSPHPicker] is `false` or the macOS version doesn't support
+ /// this feature.
+ ///
+ /// Currently defaults to `false`.
+ ///
+ /// **Note**: This implementation depends on the photos in the [Photos for macOS App](https://www.apple.com/in/macos/photos/),
+ /// if the user didn't open the app or import any photos to the app,
+ /// they will see: `No photos` or `No Photos or Videos` message even if they
+ /// have them as files on their desktop.
+ ///
+ /// Supports picking an image, multi-image, video, media, and multiple media.
+ bool useMacOSPHPicker = false;
+
+ /// Return `true` if the current macOS version supports [useMacOSPHPicker].
+ ///
+ /// The [PHPicker](https://developer.apple.com/documentation/photokit/phpickerviewcontroller)
+ /// is **supported on macOS 13.0+**
+ Future supportsPHPicker() => _hostApi.supportsPHPicker();
+
+ /// Returns `true` if [ImagePickerMacOS] should use [PHPicker](https://developer.apple.com/documentation/photokit/phpickerviewcontroller).
+ ///
+ /// See also:
+ ///
+ /// * [useMacOSPHPicker] to check whether **PHPicker** should be preferred over [file_selector_macos](https://pub.dev/packages/file_selector_macos).
+ /// * [supportsPHPicker] to verify if the current macOS version supports **PHPicker**.
+ @visibleForTesting
+ Future shouldUsePHPicker() async =>
+ await supportsPHPicker() && useMacOSPHPicker;
+
// This is soft-deprecated in the platform interface, and is only implemented
// for compatibility. Callers should be using getImageFromSource.
@override
@@ -83,8 +123,9 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
preferredCameraDevice: preferredCameraDevice));
}
- // [ImagePickerOptions] options are not currently supported. If any
- // of its fields are set, they will be silently ignored.
+ // [ImagePickerOptions] options are currently only supported when using
+ // PHPicker implementation. If any of its fields are set,
+ // they will be silently ignored.
//
// If source is `ImageSource.camera`, a `StateError` will be thrown
// unless a [cameraDelegate] is set.
@@ -97,9 +138,20 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
case ImageSource.camera:
return super.getImageFromSource(source: source);
case ImageSource.gallery:
- // TODO(stuartmorgan): Add a native implementation that can use
- // PHPickerViewController on macOS 13+, with this as a fallback for
- // older OS versions: https://github.com/flutter/flutter/issues/125829.
+ if (await shouldUsePHPicker()) {
+ final String? imagePath = (await _hostApi.pickImages(
+ _imageOptionsToImageSelectionOptions(options),
+ GeneralOptions(limit: 1),
+ ))
+ .getSuccessOrThrow()
+ .filePaths
+ .firstOrNull;
+ if (imagePath == null) {
+ return null;
+ }
+
+ return XFile(imagePath);
+ }
const XTypeGroup typeGroup =
XTypeGroup(uniformTypeIdentifiers: ['public.image']);
final XFile? file = await fileSelector
@@ -130,6 +182,18 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
preferredCameraDevice: preferredCameraDevice,
maxDuration: maxDuration);
case ImageSource.gallery:
+ if (await shouldUsePHPicker()) {
+ final String? videoPath =
+ (await _hostApi.pickVideos(GeneralOptions(limit: 1)))
+ .getSuccessOrThrow()
+ .filePaths
+ .firstOrNull;
+ if (videoPath == null) {
+ return null;
+ }
+
+ return XFile(videoPath);
+ }
const XTypeGroup typeGroup =
XTypeGroup(uniformTypeIdentifiers: ['public.movie']);
final XFile? file = await fileSelector
@@ -141,18 +205,43 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
throw UnimplementedError('Unknown ImageSource: $source');
}
- // `maxWidth`, `maxHeight`, and `imageQuality` arguments are not currently
- // supported. If any of these arguments are supplied, they will be silently
- // ignored.
+ // This is soft-deprecated in the platform interface, and is only implemented
+ // for compatibility. Callers should be using getMultiImageWithOptions.
@override
Future> getMultiImage({
double? maxWidth,
double? maxHeight,
int? imageQuality,
}) async {
- // TODO(stuartmorgan): Add a native implementation that can use
- // PHPickerViewController on macOS 13+, with this as a fallback for
- // older OS versions: https://github.com/flutter/flutter/issues/125829.
+ return getMultiImageWithOptions(
+ options: MultiImagePickerOptions(
+ imageOptions: ImageOptions(
+ imageQuality: imageQuality,
+ maxHeight: maxHeight,
+ maxWidth: maxWidth,
+ ),
+ ),
+ );
+ }
+
+ // [MultiImagePickerOptions] options are currently only
+ // supported when using PHPicker implementation. If any of these arguments are supplied, they will be silently
+ // ignored.
+ @override
+ Future> getMultiImageWithOptions({
+ MultiImagePickerOptions options = const MultiImagePickerOptions(),
+ }) async {
+ if (await shouldUsePHPicker()) {
+ final List images = (await _hostApi.pickImages(
+ _imageOptionsToImageSelectionOptions(options.imageOptions),
+ GeneralOptions(
+ limit: options.limit ?? 0,
+ ),
+ ))
+ .getSuccessOrThrow()
+ .filePaths;
+ return images.map((String imagePath) => XFile(imagePath)).toList();
+ }
const XTypeGroup typeGroup =
XTypeGroup(uniformTypeIdentifiers: ['public.image']);
final List files = await fileSelector
@@ -160,11 +249,43 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
return files;
}
- // `maxWidth`, `maxHeight`, and `imageQuality` arguments are not currently
- // supported. If any of these arguments are supplied, they will be silently
+ ImageSelectionOptions _imageOptionsToImageSelectionOptions(
+ ImageOptions imageOptions,
+ ) {
+ final int? imageQuality = imageOptions.imageQuality;
+ if (imageQuality != null && imageQuality < 0) {
+ throw ArgumentError.value(
+ imageQuality, 'imageQuality', 'quality cannot be negative');
+ }
+
+ return ImageSelectionOptions(
+ quality: imageQuality ?? 100,
+ maxSize: MaxSize(
+ width: imageOptions.maxWidth,
+ height: imageOptions.maxHeight,
+ ),
+ );
+ }
+
+ // [ImageOptions] options are currently only
+ // supported when using PHPicker implementation. If any of these arguments are supplied, they will be silently
// ignored.
@override
Future> getMedia({required MediaOptions options}) async {
+ if (await shouldUsePHPicker()) {
+ final List images = (await _hostApi.pickMedia(
+ MediaSelectionOptions(
+ imageSelectionOptions:
+ _imageOptionsToImageSelectionOptions(options.imageOptions),
+ ),
+ GeneralOptions(
+ limit: options.limit ?? (options.allowMultiple ? 0 : 1),
+ ),
+ ))
+ .getSuccessOrThrow()
+ .filePaths;
+ return images.map((String mediaPath) => XFile(mediaPath)).toList();
+ }
const XTypeGroup typeGroup = XTypeGroup(
label: 'images and videos',
extensions: ['public.image', 'public.movie']);
@@ -184,3 +305,47 @@ class ImagePickerMacOS extends CameraDelegatingImagePickerPlatform {
return files;
}
}
+
+extension _ImagePickerResultExt on ImagePickerResult {
+ /// Returns the result as an [ImagePickerSuccessResult], or throws a [PlatformException]
+ /// if the result is an [ImagePickerErrorResult].
+ ImagePickerSuccessResult getSuccessOrThrow() {
+ final ImagePickerResult result = this;
+ return switch (result) {
+ ImagePickerSuccessResult() => result,
+ ImagePickerErrorResult() => () {
+ final String errorMessage = switch (result.error) {
+ ImagePickerError.phpickerUnsupported =>
+ 'PHPicker is only supported on macOS 13.0 or newer.',
+ ImagePickerError.windowNotFound =>
+ 'No active window to present the picker.',
+ ImagePickerError.invalidImageSelection =>
+ 'One of the selected items is not an image.',
+ ImagePickerError.invalidVideoSelection =>
+ 'One of the selected items is not a video.',
+ ImagePickerError.imageLoadFailed =>
+ 'An error occurred while loading the image.',
+ ImagePickerError.videoLoadFailed =>
+ 'An error occurred while loading the video.',
+ ImagePickerError.imageConversionFailed =>
+ 'Failed to convert the NSImage to TIFF data.',
+ ImagePickerError.imageSaveFailed =>
+ 'Error saving the NSImage data to a file.',
+ ImagePickerError.imageCompressionFailed =>
+ 'Error while compressing the Data of the NSImage.',
+ ImagePickerError.multiVideoSelectionUnsupported =>
+ 'The multi-video selection is not supported.',
+ };
+ // TODO(EchoEllet): Replace PlatformException with a plugin-specific exception.
+ // This is currently implemented to maintain compatibility with the existing behavior
+ // of other implementations of `image_picker`. For more details, refer to:
+ // https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling
+ throw PlatformException(
+ code: result.error.name,
+ message: errorMessage,
+ details: result.platformErrorMessage,
+ );
+ }(),
+ };
+ }
+}
diff --git a/packages/image_picker/image_picker_macos/lib/src/messages.g.dart b/packages/image_picker/image_picker_macos/lib/src/messages.g.dart
new file mode 100644
index 000000000000..87f38080f2f3
--- /dev/null
+++ b/packages/image_picker/image_picker_macos/lib/src/messages.g.dart
@@ -0,0 +1,418 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v22.7.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
+
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+
+import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
+import 'package:flutter/services.dart';
+
+PlatformException _createConnectionError(String channelName) {
+ return PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel: "$channelName".',
+ );
+}
+
+List