Skip to content

Commit

Permalink
feat(macos): (WIP) adds support for macOS PHPicker for image_picker_m…
Browse files Browse the repository at this point in the history
…acos

- 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`
  • Loading branch information
EchoEllet committed Nov 14, 2024
1 parent 6fa1c9c commit 6843278
Show file tree
Hide file tree
Showing 32 changed files with 3,415 additions and 56 deletions.
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.3

* Updates README to include a reference to the macOS PHPicker feature.

## 1.1.2

* Adds comment for the limit parameter.
Expand Down
13 changes: 12 additions & 1 deletion packages/image_picker/image_picker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand All @@ -162,6 +162,17 @@ add a filesystem access
<true/>
```

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

<?code-excerpt "readme_excerpts.dart (Pick)"?>
Expand Down
2 changes: 1 addition & 1 deletion packages/image_picker/image_picker/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.0
Expand Down
3 changes: 2 additions & 1 deletion packages/image_picker/image_picker_macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT
## 0.3.0

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

## 0.2.1+1

Expand Down
42 changes: 38 additions & 4 deletions packages/image_picker/image_picker_macos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,45 @@

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:

<?code-excerpt "main.dart (phpicker-example)"?>
```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.
<!-- 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. -->
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

Expand All @@ -25,14 +55,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
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
```

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/
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image_picker_macos/image_picker_macos.dart';
import 'package:image_picker_macos/src/messages.g.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');
},
);
testWidgets(
'multi-video selection is not implemented',
(WidgetTester tester) async {
final ImagePickerApi hostApi = ImagePickerApi();
await expectLater(
hostApi.pickVideos(GeneralOptions(limit: 2)),
throwsA(predicate<PlatformException>(
(PlatformException e) =>
e.code == 'UNIMPLEMENTED' &&
e.message == 'Multi-video selection is not implemented',
)),
);
},
);
});
}
51 changes: 51 additions & 0 deletions packages/image_picker/image_picker_macos/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down Expand Up @@ -385,6 +396,46 @@ class _MyHomePageState extends State<MyHomePage> {
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}');
}(),
),
),
],
),
);
Expand Down
Loading

0 comments on commit 6843278

Please sign in to comment.