Skip to content

Commit 6843278

Browse files
committed
feat(macos): (WIP) adds support for macOS PHPicker for image_picker_macos
- Updates the `image_picke_macos`'s `pubspec.yaml` to add `pigeon` as dev dependency and add the `pluginClass` for Swift native code - Adds the `ImagePickerPlugin` in `image_picker_macos/macos` for native macOS plugin with support for SPM and CocoaPods with basic native unit tests - Uses the steps in https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-xctests-or-xcuitests to enable `XCTests` and `XCUITests` - Updates the `image_picker_macos_test.dart` to fix the test failure and ensure PHPicker is disabled by default - Adds a new button in the example to enable/disable PHPicker macOS implementation and enable the PHPicker by default - Updates the `README.md` of `image_picker_macos` and `image_picker` to document the usage - Removes two TODOs in `image_picker_macos.dart` as they are done with this PR - Adds TODOs that need to be done before merging the PR, some of them are questions, will be removed - Implement the getMultiImageWithOptions() since the getMultiImage is deprecated, updates getMultiImage() to delegate to getMultiImageWithOptions() since getMultiImageWithOptions() is required to access the limit property - Updates the Dart unit tests of image_picker_macos - Adds simple integration test for the example - Updates `pubspec.yaml` and `CHANGELOG.md` of `image_picker` and `image_picker_macos`
1 parent 6fa1c9c commit 6843278

32 files changed

+3415
-56
lines changed

packages/image_picker/image_picker/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.1.3
2+
3+
* Updates README to include a reference to the macOS PHPicker feature.
4+
15
## 1.1.2
26

37
* Adds comment for the limit parameter.

packages/image_picker/image_picker/README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ encourage the community to build packages that implement
153153

154154
#### macOS installation
155155

156-
Since the macOS implementation uses `file_selector`, you will need to
156+
Since the macOS implementation uses `file_selector` by default, you will need to
157157
add a filesystem access
158158
[entitlement](https://flutter.dev/to/macos-entitlements):
159159

@@ -162,6 +162,17 @@ add a filesystem access
162162
<true/>
163163
```
164164

165+
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.
166+
167+
#### macOS PHPicker
168+
169+
To use the [macOS native image picker](https://developer.apple.com/documentation/photokit/phpickerviewcontroller) which is supported on **macOS 13 and newer versions**,
170+
refer to the [image_picker_macos PHPicker](https://pub.dev/packages/image_picker_macos#phpicker) section.
171+
172+
* **on macOS 13 and newer versions**: If this feature is used, the
173+
filesystem access entitlement in the [macOS installation](#macos-installation) is not required.
174+
* **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.
175+
165176
### Example
166177

167178
<?code-excerpt "readme_excerpts.dart (Pick)"?>

packages/image_picker/image_picker/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image
33
library, and taking new pictures with the camera.
44
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker
55
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
6-
version: 1.1.2
6+
version: 1.1.3
77

88
environment:
99
sdk: ^3.3.0

packages/image_picker/image_picker_macos/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## NEXT
1+
## 0.3.0
22

33
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
4+
* Adds macOS 13+ PHPicker functionality (optional and disabled by default).
45

56
## 0.2.1+1
67

packages/image_picker/image_picker_macos/README.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,45 @@
22

33
A macOS implementation of [`image_picker`][1].
44

5+
## PHPicker
6+
7+
macOS 13.0 and newer versions supports native image picking via [PHPickerViewController][5].
8+
9+
To use this feature, add the following code to your app before calling any `image_picker` APIs:
10+
11+
<?code-excerpt "main.dart (phpicker-example)"?>
12+
```dart
13+
import 'package:image_picker_macos/image_picker_macos.dart';
14+
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
15+
// ···
16+
final ImagePickerPlatform imagePickerImplementation =
17+
ImagePickerPlatform.instance;
18+
if (imagePickerImplementation is ImagePickerMacOS) {
19+
imagePickerImplementation.useMacOSPHPicker = true;
20+
}
21+
```
22+
23+
This implementation depends on the photos in the [Photos for macOS App][6],
24+
if the user didn't open the app or import any photos to the app,
25+
they will see: `No photos` or `No Photos or Videos` message even if they
26+
have them as files on their desktop. The macOS Photos app supports importing images from an iOS device.
27+
28+
> [!NOTE]
29+
> 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.
30+
> By defaults it's disabled on all versions.
31+
532
## Limitations
633

734
`ImageSource.camera` is not supported unless a `cameraDelegate` is set.
835

936
### pickImage()
10-
The arguments `maxWidth`, `maxHeight`, and `imageQuality` are not currently supported.
37+
<!-- TODO(EchoEllet): It's possible to support those on file_selector implementation using the same platform API, should we support resizing and compressing for file_selector implementation? Will return new temp file path. -->
38+
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.
39+
40+
The argument `requestFullMetadata` is unsupported on macOS.
1141

1242
### pickVideo()
13-
The argument `maxDuration` is not currently supported.
43+
The argument `maxDuration` is not supported even when using the [PHPicker](#phpicker) implementation.
1444

1545
## Usage
1646

@@ -25,14 +55,18 @@ should add it to your `pubspec.yaml` as usual.
2555

2656
### Entitlements
2757

28-
This package is currently implemented using [`file_selector`][3], so you will
29-
need to add a read-only file acces [entitlement][4]:
58+
This package’s default implementation relies on [file_selector][3],
59+
which requires the following read-only file access entitlement:
3060
```xml
3161
<key>com.apple.security.files.user-selected.read-only</key>
3262
<true/>
3363
```
3464

65+
If you're using the [PHPicker](#phpicker) and require at **least macOS 13** to run the app, this entitlement is not required.
66+
3567
[1]: https://pub.dev/packages/image_picker
3668
[2]: https://flutter.dev/to/endorsed-federated-plugin
3769
[3]: https://pub.dev/packages/file_selector
3870
[4]: https://flutter.dev/to/macos-entitlements
71+
[5]: https://developer.apple.com/documentation/photokit/phpickerviewcontroller
72+
[6]: https://www.apple.com/in/macos/photos/
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:example/main.dart';
6+
import 'package:flutter/services.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:image_picker_macos/image_picker_macos.dart';
9+
import 'package:image_picker_macos/src/messages.g.dart';
10+
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
11+
import 'package:integration_test/integration_test.dart';
12+
13+
ImagePickerMacOS get requireMacOSImplementation {
14+
final ImagePickerPlatform imagePickerImplementation =
15+
ImagePickerPlatform.instance;
16+
if (imagePickerImplementation is! ImagePickerMacOS) {
17+
fail('Expected the implementation to be $ImagePickerMacOS');
18+
}
19+
return imagePickerImplementation;
20+
}
21+
22+
void main() {
23+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
24+
group('example', () {
25+
testWidgets(
26+
'Pressing the PHPicker toggle button updates it correctly',
27+
(WidgetTester tester) async {
28+
final ImagePickerMacOS imagePickerImplementation =
29+
requireMacOSImplementation;
30+
expect(imagePickerImplementation.useMacOSPHPicker, false,
31+
reason: 'The default is to not using PHPicker');
32+
33+
await tester.pumpWidget(const MyApp());
34+
final Finder togglePHPickerFinder =
35+
find.byTooltip('toggle macOS PHPPicker');
36+
expect(togglePHPickerFinder, findsOneWidget);
37+
38+
await tester.tap(togglePHPickerFinder);
39+
expect(imagePickerImplementation.useMacOSPHPicker, true,
40+
reason: 'Pressing the toggle button should update it correctly');
41+
42+
await tester.tap(togglePHPickerFinder);
43+
expect(imagePickerImplementation.useMacOSPHPicker, false,
44+
reason: 'Pressing the toggle button should update it correctly');
45+
},
46+
);
47+
testWidgets(
48+
'multi-video selection is not implemented',
49+
(WidgetTester tester) async {
50+
final ImagePickerApi hostApi = ImagePickerApi();
51+
await expectLater(
52+
hostApi.pickVideos(GeneralOptions(limit: 2)),
53+
throwsA(predicate<PlatformException>(
54+
(PlatformException e) =>
55+
e.code == 'UNIMPLEMENTED' &&
56+
e.message == 'Multi-video selection is not implemented',
57+
)),
58+
);
59+
},
60+
);
61+
});
62+
}

packages/image_picker/image_picker_macos/example/lib/main.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,22 @@ import 'dart:async';
88
import 'dart:io';
99

1010
import 'package:flutter/material.dart';
11+
// #docregion phpicker-example
12+
import 'package:image_picker_macos/image_picker_macos.dart';
1113
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
14+
// #enddocregion phpicker-example
1215
import 'package:mime/mime.dart';
1316
import 'package:video_player/video_player.dart';
1417

1518
void main() {
19+
// Set to use macOS PHPicker.
20+
// #docregion phpicker-example
21+
final ImagePickerPlatform imagePickerImplementation =
22+
ImagePickerPlatform.instance;
23+
if (imagePickerImplementation is ImagePickerMacOS) {
24+
imagePickerImplementation.useMacOSPHPicker = true;
25+
}
26+
// #enddocregion phpicker-example
1627
runApp(const MyApp());
1728
}
1829

@@ -385,6 +396,46 @@ class _MyHomePageState extends State<MyHomePage> {
385396
child: const Icon(Icons.videocam),
386397
),
387398
),
399+
Padding(
400+
padding: const EdgeInsets.only(top: 16.0),
401+
child: FloatingActionButton(
402+
onPressed: () {
403+
void showSnackbarText(String text) {
404+
ScaffoldMessenger.of(context)
405+
..clearSnackBars()
406+
..showSnackBar(
407+
SnackBar(content: Text(text)),
408+
);
409+
}
410+
411+
if (_picker is! ImagePickerMacOS) {
412+
throw StateError(
413+
'Expected the implementation to be $ImagePickerMacOS but was ${_picker.runtimeType}');
414+
}
415+
416+
if (_picker.useMacOSPHPicker) {
417+
_picker.useMacOSPHPicker = false;
418+
setState(() {});
419+
showSnackbarText('Switched to file_picker implementation.');
420+
} else {
421+
_picker.useMacOSPHPicker = true;
422+
setState(() {});
423+
showSnackbarText(
424+
'Switched to macOS PHPPicker implementation.');
425+
}
426+
},
427+
tooltip: 'toggle macOS PHPPicker',
428+
child: () {
429+
if (_picker is ImagePickerMacOS) {
430+
return _picker.useMacOSPHPicker
431+
? const Icon(Icons.apple)
432+
: const Icon(Icons.file_open);
433+
}
434+
throw StateError(
435+
'Expected the implementation to be $ImagePickerMacOS but was ${_picker.runtimeType}');
436+
}(),
437+
),
438+
),
388439
],
389440
),
390441
);

0 commit comments

Comments
 (0)