[image_picker] Add desktop support - platform interface (#4161)

Platform interface portion of https://github.com/flutter/packages/pull/3882

Adds CameraDelegatingImagePickerPlatform and ImagePickerCameraDelegate, and supportsImageSource

Part of https://github.com/flutter/flutter/issues/102115
Part of https://github.com/flutter/flutter/issues/102320
Part of https://github.com/flutter/flutter/issues/85100
This commit is contained in:
stuartmorgan
2023-06-08 19:51:41 -04:00
committed by GitHub
parent f6633b20d0
commit afe2f05c1a
7 changed files with 239 additions and 4 deletions

View File

@ -1,3 +1,11 @@
## 2.7.0
* Adds `CameraDelegatingImagePickerPlatform` as a base class for platform
implementations that don't support `ImageSource.camera`, but allow for an-
implementation to be provided at the application level via implementation
of `CameraDelegatingImagePickerPlatform`.
* Adds `supportsImageSource` to check source support at runtime.
## 2.6.4
* Adds compatibility with `http` 1.0.
@ -32,7 +40,7 @@
* Adds `requestFullMetadata` option that allows disabling extra permission requests
on certain platforms.
* Moves optional image picking parameters to `ImagePickerOptions` class.
* Minor fixes for new analysis options.
* Minor fixes for new analysis options.
## 2.4.4

View File

@ -32,8 +32,6 @@ abstract class ImagePickerPlatform extends PlatformInterface {
/// Platform-specific plugins should set this with their own platform-specific
/// class that extends [ImagePickerPlatform] when they register themselves.
// TODO(amirh): Extract common platform interface logic.
// https://github.com/flutter/flutter/issues/43368
static set instance(ImagePickerPlatform instance) {
PlatformInterface.verify(instance, _token);
_instance = instance;
@ -305,4 +303,75 @@ abstract class ImagePickerPlatform extends PlatformInterface {
);
return pickedImages ?? <XFile>[];
}
/// Returns true if the implementation supports [source].
///
/// Defaults to true for the original image sources, `gallery` and `camera`,
/// for backwards compatibility.
bool supportsImageSource(ImageSource source) {
return source == ImageSource.gallery || source == ImageSource.camera;
}
}
/// A base class for an [ImagePickerPlatform] implementation that does not
/// directly support [ImageSource.camera], but supports delegating to a
/// provided [ImagePickerCameraDelegate].
abstract class CameraDelegatingImagePickerPlatform extends ImagePickerPlatform {
/// A delegate to respond to calls that use [ImageSource.camera].
///
/// When it is null, attempting to use [ImageSource.camera] will throw a
/// [StateError].
ImagePickerCameraDelegate? cameraDelegate;
@override
bool supportsImageSource(ImageSource source) {
if (source == ImageSource.camera) {
return cameraDelegate != null;
}
return super.supportsImageSource(source);
}
@override
Future<XFile?> getImageFromSource({
required ImageSource source,
ImagePickerOptions options = const ImagePickerOptions(),
}) async {
if (source == ImageSource.camera) {
final ImagePickerCameraDelegate? delegate = cameraDelegate;
if (delegate == null) {
throw StateError(
'This implementation of ImagePickerPlatform requires a '
'"cameraDelegate" in order to use ImageSource.camera');
}
return delegate.takePhoto(
options: ImagePickerCameraDelegateOptions(
preferredCameraDevice: options.preferredCameraDevice,
));
}
return super.getImageFromSource(source: source, options: options);
}
@override
Future<XFile?> getVideo({
required ImageSource source,
CameraDevice preferredCameraDevice = CameraDevice.rear,
Duration? maxDuration,
}) async {
if (source == ImageSource.camera) {
final ImagePickerCameraDelegate? delegate = cameraDelegate;
if (delegate == null) {
throw StateError(
'This implementation of ImagePickerPlatform requires a '
'"cameraDelegate" in order to use ImageSource.camera');
}
return delegate.takeVideo(
options: ImagePickerCameraDelegateOptions(
preferredCameraDevice: preferredCameraDevice,
maxVideoDuration: maxDuration));
}
return super.getVideo(
source: source,
preferredCameraDevice: preferredCameraDevice,
maxDuration: maxDuration);
}
}

View File

@ -0,0 +1,53 @@
// 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:cross_file/cross_file.dart';
import 'package:flutter/foundation.dart' show immutable;
import 'camera_device.dart';
/// Options for [ImagePickerCameraDelegate] methods.
///
/// New options may be added in the future.
@immutable
class ImagePickerCameraDelegateOptions {
/// Creates a new set of options for taking an image or video.
const ImagePickerCameraDelegateOptions({
this.preferredCameraDevice = CameraDevice.rear,
this.maxVideoDuration,
});
/// The camera device to default to, if available.
///
/// Defaults to [CameraDevice.rear].
final CameraDevice preferredCameraDevice;
/// The maximum duration to allow when recording a video.
///
/// Defaults to null, meaning no maximum duration.
final Duration? maxVideoDuration;
}
/// A delegate for `ImagePickerPlatform` implementations that do not provide
/// a camera implementation, or that have a default but allow substituting an
/// alternate implementation.
abstract class ImagePickerCameraDelegate {
/// Takes a photo with the given [options] and returns an [XFile] to the
/// resulting image file.
///
/// Returns null if the photo could not be taken, or the user cancelled.
Future<XFile?> takePhoto({
ImagePickerCameraDelegateOptions options =
const ImagePickerCameraDelegateOptions(),
});
/// Records a video with the given [options] and returns an [XFile] to the
/// resulting video file.
///
/// Returns null if the video could not be recorded, or the user cancelled.
Future<XFile?> takeVideo({
ImagePickerCameraDelegateOptions options =
const ImagePickerCameraDelegateOptions(),
});
}

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export 'camera_delegate.dart';
export 'camera_device.dart';
export 'image_options.dart';
export 'image_picker_options.dart';

View File

@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/image_picker/
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
version: 2.6.4
version: 2.7.0
environment:
sdk: ">=2.18.0 <4.0.0"

View File

@ -0,0 +1,104 @@
// 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:flutter_test/flutter_test.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
void main() {
group('ImagePickerPlatform', () {
test('supportsImageSource defaults to true for original values', () async {
final ImagePickerPlatform implementation = FakeImagePickerPlatform();
expect(implementation.supportsImageSource(ImageSource.camera), true);
expect(implementation.supportsImageSource(ImageSource.gallery), true);
});
});
group('CameraDelegatingImagePickerPlatform', () {
test(
'supportsImageSource returns false for camera when there is no delegate',
() async {
final FakeCameraDelegatingImagePickerPlatform implementation =
FakeCameraDelegatingImagePickerPlatform();
expect(implementation.supportsImageSource(ImageSource.camera), false);
});
test('supportsImageSource returns true for camera when there is a delegate',
() async {
final FakeCameraDelegatingImagePickerPlatform implementation =
FakeCameraDelegatingImagePickerPlatform();
implementation.cameraDelegate = FakeCameraDelegate();
expect(implementation.supportsImageSource(ImageSource.camera), true);
});
test('getImageFromSource for camera throws if delegate is not set',
() async {
final FakeCameraDelegatingImagePickerPlatform implementation =
FakeCameraDelegatingImagePickerPlatform();
await expectLater(
implementation.getImageFromSource(source: ImageSource.camera),
throwsStateError);
});
test('getVideo for camera throws if delegate is not set', () async {
final FakeCameraDelegatingImagePickerPlatform implementation =
FakeCameraDelegatingImagePickerPlatform();
await expectLater(implementation.getVideo(source: ImageSource.camera),
throwsStateError);
});
test('getImageFromSource for camera calls delegate if set', () async {
const String fakePath = '/tmp/foo';
final FakeCameraDelegatingImagePickerPlatform implementation =
FakeCameraDelegatingImagePickerPlatform();
implementation.cameraDelegate =
FakeCameraDelegate(result: XFile(fakePath));
expect(
(await implementation.getImageFromSource(source: ImageSource.camera))!
.path,
fakePath);
});
test('getVideo for camera calls delegate if set', () async {
const String fakePath = '/tmp/foo';
final FakeCameraDelegatingImagePickerPlatform implementation =
FakeCameraDelegatingImagePickerPlatform();
implementation.cameraDelegate =
FakeCameraDelegate(result: XFile(fakePath));
expect((await implementation.getVideo(source: ImageSource.camera))!.path,
fakePath);
});
});
}
class FakeImagePickerPlatform extends ImagePickerPlatform {}
class FakeCameraDelegatingImagePickerPlatform
extends CameraDelegatingImagePickerPlatform {}
class FakeCameraDelegate extends ImagePickerCameraDelegate {
FakeCameraDelegate({this.result});
XFile? result;
@override
Future<XFile?> takePhoto(
{ImagePickerCameraDelegateOptions options =
const ImagePickerCameraDelegateOptions()}) async {
return result;
}
@override
Future<XFile?> takeVideo(
{ImagePickerCameraDelegateOptions options =
const ImagePickerCameraDelegateOptions()}) async {
return result;
}
}