mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 15:23:25 +08:00
[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:
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
});
|
||||
}
|
@ -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';
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user