Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[image_picker_macos] macOS native image picker #8079

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

EchoEllet
Copy link
Contributor

@EchoEllet EchoEllet commented Nov 14, 2024

Adds support for macOS native image picker for image_picker_macos.

An alternative implementation that is based on PHPickerViewController instead of file_selector.

FlutterNativeMacOSImagePicker.mp4
macOS native image picker window

Usage

Similar to the Android Photo Picker in image_picker_android:

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;
  }

Note

It's only supported on macOS +13, on macOS 12 and older versions, will fallback to the file_selector_macos implementation.

Related Issues

Limiations

  • Supports maxWidth, maxHeight, imageQuality, and limit when using the PHPicker implementation.
  • Supports picking image, multi-image, video, media, multi-media. The multi-media supports picking images and videos.
  • Doesn't support maxDuration of video selection, and requestFullMetadata (iOS-specific).
  • The user can only pick photos from the macOS Photos app. The user will see a No Photos message if they don't have photos within the app.

Motivation

The macOS native picker only supports picking images, videos, and media from the Photos for macOS app.

If the user has photos in a directory, they will need to open the app and import the images they need, however, this app integrates with other macOS apps and the Apple ecosystem, and it supports features such as importing pictures seamlessly from an iOS device, albums, favorites, recently deleted, duplicates.

Screenshot

Importing iOS photos to the macOS Photo Picker

Tip

It doesn't require file read-only access entitlement.

IMO

it's better to keep this implementation optional instead of making it as default, the developer can provide the option to switch between implementations based on the user preference since it's not common on desktops to have a photos app like mobile platforms, maybe provide a method to check if it's supported:

ImagePicker().supportsPHPicker();

// OR

final ImagePickerPlatform imagePickerImplementation =
      ImagePickerPlatform.instance;
  if (imagePickerImplementation is ImagePickerMacOS) {
    imagePickerImplementation.supportsPHPicker();
  }

Testing

This branch has been manually tested on macOS 14.6.1.
Native and Dart Unit tests have been added, though unfortunately, native UI tests are missing (see #70234). I left a TODO since currently it doesn't seem to be possible.

Additional Context

Related Discord Threads:

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Copy link
Contributor Author

@EchoEllet EchoEllet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left TODOs in the code that should be addressed in this PR before continuing further except the TODO of adding macOS native UI tests.

I welcome any feedback and suggestions.

Comment on lines +11 to +18
private func createTestImage(size: NSSize) -> NSImage {
let image = NSImage(size: size)
image.lockFocus()
NSColor.white.set()
NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill()
image.unlockFocus()
return image
}
Copy link
Contributor Author

@EchoEllet EchoEllet Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated in both ImageCompressTests and ImageResizeTests, should I extract it?
Most unit tests of plugins on this repo are implemented with one file RunnerTests.

Comment on lines +11 to +18
private func createTestImage(size: NSSize) -> NSImage {
let image = NSImage(size: size)
image.lockFocus()
NSColor.black.set()
NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill()
image.unlockFocus()
return image
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated.

/// * [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<bool> shouldUsePHPicker() async =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +28 to +30
// TODO(EchoEllet): Is there a convention for the error code? ImageConversionError or IMAGE_CONVERSION_ERROR or image-conversion-error. Update all codes.
throw PigeonError(
code: "ImageConversionError", message: "Failed to convert NSImage to TIFF data.",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably image-conversion-error?

Comment on lines +22 to +23
assert(quality != 100, "Quality 100 means no compression.")
assert(quality >= 0, "Quality can't be negative.")
Copy link
Contributor Author

@EchoEllet EchoEllet Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I remove the asserts from the platform side since it will crash the app on failure when not running using Xcode (flutter run -d macos)?

@EchoEllet
Copy link
Contributor Author

EchoEllet commented Nov 14, 2024

Mac_arm64 macos_repo_checks
Tasks failed: Swift format,validate iOS and macOS podspecs

All Swift files are formatted except the generated Messages.g.swift.

I ran the script dart run script/tool/bin/flutter_plugin_tools.dart format --packages image_picker_macos to format the Messages.g.swift though the output produced some warnings. Should they ignored?

Output

image_picker/image_picker_macos/macos/image_picker_macos/Sources/image_picker_macos/Messages.g.swift:166:15: warning: [TypeNamesShouldBeCapitalized] rename the class 'messagesPigeonCodecReader' using UpperCamelCase; for example, 'MessagesPigeonCodecReader'
image_picker/image_picker_macos/macos/image_picker_macos/Sources/image_picker_macos/Messages.g.swift:183:15: warning: [TypeNamesShouldBeCapitalized] rename the class 'messagesPigeonCodecWriter' using UpperCamelCase; for example, 'MessagesPigeonCodecWriter'
image_picker/image_picker_macos/macos/image_picker_macos/Sources/image_picker_macos/Messages.g.swift:203:15: warning: [TypeNamesShouldBeCapitalized] rename the class 'messagesPigeonCodecReaderWriter' using UpperCamelCase; for example, 'MessagesPigeonCodecReaderWriter'
image_picker/image_picker_macos/macos/image_picker_macos/Sources/image_picker_macos/Messages.g.swift:213:7: warning: [TypeNamesShouldBeCapitalized] rename the class 'messagesPigeonCodec' using UpperCamelCase; for example, 'MessagesPigeonCodec'
Swift linter found issues. See above for linter output.

These files are not formatted correctly (see diff below):
packages/image_picker/image_picker_macos/lib/src/messages.g.dart
packages/image_picker/image_picker_macos/test/test_api.g.dart

Those Dart files are generated, should generated files be formatted on this repo?
Formatted using dart format since the format tool within this repo didn't format them.

image_picker_macos/macos/image_picker_macos/Sources/image_picker_macos/ImagePickerImpl.swift:462:31: error: file contains invalid or unrecognized Swift syntax.
Failed to format Swift files: exit code 1.

I'm using Xcode 16.1 with Swift 6, maybe the CI uses Swift 5?

- NOTE | [OSX] xcodebuild: Pods.xcodeproj: warning: The macOS deployment target 'MACOSX_DEPLOYMENT_TARGET' is set to 10.11, but the range of supported deployment target versions is 10.13 to 14.0.99. (in target 'image_picker_macos' from project 'Pods')

Should I update the minimum to 10.13 instead of 10.11 since file_selector_macos requires at least 10.14 which is a dependency of image_picker_macos? Updated to 10.14 since that's the minimum supported version by Flutter stable.

- WARN | [OSX] xcodebuild: /Volumes/Work/s/w/ir/x/w/packages/packages/image_picker/image_picker_macos/macos/image_picker_macos/Sources/image_picker_macos/ImagePickerImpl.swift:279:15: warning: passing argument of non-sendable type 'NSItemProvider' outside of main actor-isolated context may introduce data

I'm new to Apple/Xcode/Swift ecosystem, should I only mark processAndSave with @MainActor (likely to be inefficient) or instead load the data representation and pass it to PickVideoHandler and PickImageHandler or maybe annotate only the part that uses NSItemProvider?

@EchoEllet
Copy link
Contributor Author

EchoEllet commented Nov 14, 2024

The doc comments of image_picker have not been updated.

temporary file could not be created (iOS only)
Future<XFile?> pickImage

This is also possible with the native macOS implementation (when PHPicker is used). Should I update those parts of the docs to reflect the change?

@EchoEllet
Copy link
Contributor Author

EchoEllet commented Nov 14, 2024

The ImageSource is unsupported for both implementations, should override the supportsImageSource?

@override
bool supportsImageSource(ImageSource source) {
  if (source == ImageSource.camera) {
    return cameraDelegate != null;
  }
  return false;
}

The arguments maxWidth, maxHeight, imageQuality, and limit are only supported when using the PHPicker implementation;

It's also possible to support those on file_selector implementation using the same platform API, should we support resizing and compressing for file_selector implementation?

EchoEllet added a commit to EchoEllet/packages that referenced this pull request Nov 14, 2024
noActiveWindow()
return
}
// TODO(EchoEllet): IMPORTANT The window size of the picker is smaller than expected, see the video in https://discord.com/channels/608014603317936148/1295165633931120642/1295470850283147335
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an important issue to fix since it affects the user experience, see the video in this PR.

_imageOptionsToImageSelectionOptions(options.imageOptions),
),
GeneralOptions(
limit: options.limit ?? (options.allowMultiple ? 0 : 1),
Copy link
Contributor Author

@EchoEllet EchoEllet Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be incorrect and needs validation similarly to image_picker_android. The current code doesn't check allowMultiple in case limit was provided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is validation from the app-facing package (image_picker) for all methods, do we still need to verify from the platform implementation side?

@EchoEllet EchoEllet force-pushed the feat/phpicker-image-picker-macos branch 2 times, most recently from c0177fb to 277a923 Compare November 17, 2024 11:31
EchoEllet added a commit to EchoEllet/packages that referenced this pull request Nov 17, 2024
@EchoEllet EchoEllet force-pushed the feat/phpicker-image-picker-macos branch from 277a923 to 6a489e6 Compare November 17, 2024 11:43
EchoEllet added a commit to EchoEllet/packages that referenced this pull request Nov 17, 2024
@EchoEllet EchoEllet force-pushed the feat/phpicker-image-picker-macos branch from 6a489e6 to d3535ee Compare November 17, 2024 11:55
…mated), deprecated @NSApplicationMain attribute

Signed-off-by: Ellet <[email protected]>
…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`

Signed-off-by: Ellet <[email protected]>
@EchoEllet EchoEllet force-pushed the feat/phpicker-image-picker-macos branch from d3535ee to c9122f9 Compare November 17, 2024 12:01
@EchoEllet EchoEllet marked this pull request as ready for review November 24, 2024 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[image_picker] Support image/video restrictions on macOS [image_picker] Use PHPicker for macOS 13+
1 participant