mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 23:51:55 +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
|
## 2.6.4
|
||||||
|
|
||||||
* Adds compatibility with `http` 1.0.
|
* Adds compatibility with `http` 1.0.
|
||||||
@ -32,7 +40,7 @@
|
|||||||
* Adds `requestFullMetadata` option that allows disabling extra permission requests
|
* Adds `requestFullMetadata` option that allows disabling extra permission requests
|
||||||
on certain platforms.
|
on certain platforms.
|
||||||
* Moves optional image picking parameters to `ImagePickerOptions` class.
|
* Moves optional image picking parameters to `ImagePickerOptions` class.
|
||||||
* Minor fixes for new analysis options.
|
* Minor fixes for new analysis options.
|
||||||
|
|
||||||
## 2.4.4
|
## 2.4.4
|
||||||
|
|
||||||
|
@ -32,8 +32,6 @@ abstract class ImagePickerPlatform extends PlatformInterface {
|
|||||||
|
|
||||||
/// Platform-specific plugins should set this with their own platform-specific
|
/// Platform-specific plugins should set this with their own platform-specific
|
||||||
/// class that extends [ImagePickerPlatform] when they register themselves.
|
/// 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) {
|
static set instance(ImagePickerPlatform instance) {
|
||||||
PlatformInterface.verify(instance, _token);
|
PlatformInterface.verify(instance, _token);
|
||||||
_instance = instance;
|
_instance = instance;
|
||||||
@ -305,4 +303,75 @@ abstract class ImagePickerPlatform extends PlatformInterface {
|
|||||||
);
|
);
|
||||||
return pickedImages ?? <XFile>[];
|
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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
export 'camera_delegate.dart';
|
||||||
export 'camera_device.dart';
|
export 'camera_device.dart';
|
||||||
export 'image_options.dart';
|
export 'image_options.dart';
|
||||||
export 'image_picker_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
|
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
|
# 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
|
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
|
||||||
version: 2.6.4
|
version: 2.7.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.18.0 <4.0.0"
|
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