From 872d04cda5f294a3dacafeabde7c5c5425884738 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 7 Jun 2023 16:09:18 -0400 Subject: [PATCH 1/2] Apply platform interface changes --- .../CHANGELOG.md | 9 +- .../image_picker_platform.dart | 73 ++++++++++++- .../lib/src/types/camera_delegate.dart | 53 +++++++++ .../lib/src/types/types.dart | 1 + .../pubspec.yaml | 2 +- .../test/image_picker_platform_test.dart | 103 ++++++++++++++++++ ... => method_channel_image_picker_test.dart} | 0 7 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 packages/image_picker/image_picker_platform_interface/lib/src/types/camera_delegate.dart create mode 100644 packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart rename packages/image_picker/image_picker_platform_interface/test/{new_method_channel_image_picker_test.dart => method_channel_image_picker_test.dart} (100%) diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 1308001548b..90b0d6abde5 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,5 +1,10 @@ -## NEXT +## 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. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. * Aligns Dart and Flutter SDK constraints. @@ -31,7 +36,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 diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart index c8942cd2da0..e01caca1461 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart @@ -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 ?? []; } + + /// 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 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 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); + } } diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/camera_delegate.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/camera_delegate.dart new file mode 100644 index 00000000000..39584c923b0 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/camera_delegate.dart @@ -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 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 takeVideo({ + ImagePickerCameraDelegateOptions options = + const ImagePickerCameraDelegateOptions(), + }); +} diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/types.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/types.dart index fbe12e8e825..fcb76ccefa2 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/types/types.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/types.dart @@ -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'; diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index 5bc22aecd9e..d71ae3abf13 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -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.3 +version: 2.7.0 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart b/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart new file mode 100644 index 00000000000..258aad09d0a --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart @@ -0,0 +1,103 @@ +// 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(); + + expectLater(implementation.getImageFromSource(source: ImageSource.camera), + throwsStateError); + }); + + test('getVideo for camera throws if delegate is not set', () async { + final FakeCameraDelegatingImagePickerPlatform implementation = + FakeCameraDelegatingImagePickerPlatform(); + + 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 takePhoto( + {ImagePickerCameraDelegateOptions options = + const ImagePickerCameraDelegateOptions()}) async { + return result; + } + + @override + Future takeVideo( + {ImagePickerCameraDelegateOptions options = + const ImagePickerCameraDelegateOptions()}) async { + return result; + } +} diff --git a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/method_channel_image_picker_test.dart similarity index 100% rename from packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart rename to packages/image_picker/image_picker_platform_interface/test/method_channel_image_picker_test.dart From 86c20a4054b104642c875b288afa469af156293e Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 7 Jun 2023 17:03:00 -0400 Subject: [PATCH 2/2] Add missing awaits --- .../test/image_picker_platform_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart b/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart index 258aad09d0a..89dc1ae382d 100644 --- a/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart +++ b/packages/image_picker/image_picker_platform_interface/test/image_picker_platform_test.dart @@ -39,7 +39,8 @@ void main() { final FakeCameraDelegatingImagePickerPlatform implementation = FakeCameraDelegatingImagePickerPlatform(); - expectLater(implementation.getImageFromSource(source: ImageSource.camera), + await expectLater( + implementation.getImageFromSource(source: ImageSource.camera), throwsStateError); }); @@ -47,7 +48,7 @@ void main() { final FakeCameraDelegatingImagePickerPlatform implementation = FakeCameraDelegatingImagePickerPlatform(); - expectLater(implementation.getVideo(source: ImageSource.camera), + await expectLater(implementation.getVideo(source: ImageSource.camera), throwsStateError); });