Skip to content

Commit afe2f05

Browse files
[image_picker] Add desktop support - platform interface (#4161)
Platform interface portion of #3882 Adds CameraDelegatingImagePickerPlatform and ImagePickerCameraDelegate, and supportsImageSource Part of flutter/flutter#102115 Part of flutter/flutter#102320 Part of flutter/flutter#85100
1 parent f6633b2 commit afe2f05

File tree

7 files changed

+239
-4
lines changed

7 files changed

+239
-4
lines changed

packages/image_picker/image_picker_platform_interface/CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 2.7.0
2+
3+
* Adds `CameraDelegatingImagePickerPlatform` as a base class for platform
4+
implementations that don't support `ImageSource.camera`, but allow for an-
5+
implementation to be provided at the application level via implementation
6+
of `CameraDelegatingImagePickerPlatform`.
7+
* Adds `supportsImageSource` to check source support at runtime.
8+
19
## 2.6.4
210

311
* Adds compatibility with `http` 1.0.
@@ -32,7 +40,7 @@
3240
* Adds `requestFullMetadata` option that allows disabling extra permission requests
3341
on certain platforms.
3442
* Moves optional image picking parameters to `ImagePickerOptions` class.
35-
* Minor fixes for new analysis options.
43+
* Minor fixes for new analysis options.
3644

3745
## 2.4.4
3846

packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ abstract class ImagePickerPlatform extends PlatformInterface {
3232

3333
/// Platform-specific plugins should set this with their own platform-specific
3434
/// class that extends [ImagePickerPlatform] when they register themselves.
35-
// TODO(amirh): Extract common platform interface logic.
36-
// https://github.com/flutter/flutter/issues/43368
3735
static set instance(ImagePickerPlatform instance) {
3836
PlatformInterface.verify(instance, _token);
3937
_instance = instance;
@@ -305,4 +303,75 @@ abstract class ImagePickerPlatform extends PlatformInterface {
305303
);
306304
return pickedImages ?? <XFile>[];
307305
}
306+
307+
/// Returns true if the implementation supports [source].
308+
///
309+
/// Defaults to true for the original image sources, `gallery` and `camera`,
310+
/// for backwards compatibility.
311+
bool supportsImageSource(ImageSource source) {
312+
return source == ImageSource.gallery || source == ImageSource.camera;
313+
}
314+
}
315+
316+
/// A base class for an [ImagePickerPlatform] implementation that does not
317+
/// directly support [ImageSource.camera], but supports delegating to a
318+
/// provided [ImagePickerCameraDelegate].
319+
abstract class CameraDelegatingImagePickerPlatform extends ImagePickerPlatform {
320+
/// A delegate to respond to calls that use [ImageSource.camera].
321+
///
322+
/// When it is null, attempting to use [ImageSource.camera] will throw a
323+
/// [StateError].
324+
ImagePickerCameraDelegate? cameraDelegate;
325+
326+
@override
327+
bool supportsImageSource(ImageSource source) {
328+
if (source == ImageSource.camera) {
329+
return cameraDelegate != null;
330+
}
331+
return super.supportsImageSource(source);
332+
}
333+
334+
@override
335+
Future<XFile?> getImageFromSource({
336+
required ImageSource source,
337+
ImagePickerOptions options = const ImagePickerOptions(),
338+
}) async {
339+
if (source == ImageSource.camera) {
340+
final ImagePickerCameraDelegate? delegate = cameraDelegate;
341+
if (delegate == null) {
342+
throw StateError(
343+
'This implementation of ImagePickerPlatform requires a '
344+
'"cameraDelegate" in order to use ImageSource.camera');
345+
}
346+
return delegate.takePhoto(
347+
options: ImagePickerCameraDelegateOptions(
348+
preferredCameraDevice: options.preferredCameraDevice,
349+
));
350+
}
351+
return super.getImageFromSource(source: source, options: options);
352+
}
353+
354+
@override
355+
Future<XFile?> getVideo({
356+
required ImageSource source,
357+
CameraDevice preferredCameraDevice = CameraDevice.rear,
358+
Duration? maxDuration,
359+
}) async {
360+
if (source == ImageSource.camera) {
361+
final ImagePickerCameraDelegate? delegate = cameraDelegate;
362+
if (delegate == null) {
363+
throw StateError(
364+
'This implementation of ImagePickerPlatform requires a '
365+
'"cameraDelegate" in order to use ImageSource.camera');
366+
}
367+
return delegate.takeVideo(
368+
options: ImagePickerCameraDelegateOptions(
369+
preferredCameraDevice: preferredCameraDevice,
370+
maxVideoDuration: maxDuration));
371+
}
372+
return super.getVideo(
373+
source: source,
374+
preferredCameraDevice: preferredCameraDevice,
375+
maxDuration: maxDuration);
376+
}
308377
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:cross_file/cross_file.dart';
6+
import 'package:flutter/foundation.dart' show immutable;
7+
8+
import 'camera_device.dart';
9+
10+
/// Options for [ImagePickerCameraDelegate] methods.
11+
///
12+
/// New options may be added in the future.
13+
@immutable
14+
class ImagePickerCameraDelegateOptions {
15+
/// Creates a new set of options for taking an image or video.
16+
const ImagePickerCameraDelegateOptions({
17+
this.preferredCameraDevice = CameraDevice.rear,
18+
this.maxVideoDuration,
19+
});
20+
21+
/// The camera device to default to, if available.
22+
///
23+
/// Defaults to [CameraDevice.rear].
24+
final CameraDevice preferredCameraDevice;
25+
26+
/// The maximum duration to allow when recording a video.
27+
///
28+
/// Defaults to null, meaning no maximum duration.
29+
final Duration? maxVideoDuration;
30+
}
31+
32+
/// A delegate for `ImagePickerPlatform` implementations that do not provide
33+
/// a camera implementation, or that have a default but allow substituting an
34+
/// alternate implementation.
35+
abstract class ImagePickerCameraDelegate {
36+
/// Takes a photo with the given [options] and returns an [XFile] to the
37+
/// resulting image file.
38+
///
39+
/// Returns null if the photo could not be taken, or the user cancelled.
40+
Future<XFile?> takePhoto({
41+
ImagePickerCameraDelegateOptions options =
42+
const ImagePickerCameraDelegateOptions(),
43+
});
44+
45+
/// Records a video with the given [options] and returns an [XFile] to the
46+
/// resulting video file.
47+
///
48+
/// Returns null if the video could not be recorded, or the user cancelled.
49+
Future<XFile?> takeVideo({
50+
ImagePickerCameraDelegateOptions options =
51+
const ImagePickerCameraDelegateOptions(),
52+
});
53+
}

packages/image_picker/image_picker_platform_interface/lib/src/types/types.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
export 'camera_delegate.dart';
56
export 'camera_device.dart';
67
export 'image_options.dart';
78
export 'image_picker_options.dart';

packages/image_picker/image_picker_platform_interface/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/image_picker/
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
55
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
66
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
7-
version: 2.6.4
7+
version: 2.7.0
88

99
environment:
1010
sdk: ">=2.18.0 <4.0.0"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
7+
8+
void main() {
9+
group('ImagePickerPlatform', () {
10+
test('supportsImageSource defaults to true for original values', () async {
11+
final ImagePickerPlatform implementation = FakeImagePickerPlatform();
12+
13+
expect(implementation.supportsImageSource(ImageSource.camera), true);
14+
expect(implementation.supportsImageSource(ImageSource.gallery), true);
15+
});
16+
});
17+
18+
group('CameraDelegatingImagePickerPlatform', () {
19+
test(
20+
'supportsImageSource returns false for camera when there is no delegate',
21+
() async {
22+
final FakeCameraDelegatingImagePickerPlatform implementation =
23+
FakeCameraDelegatingImagePickerPlatform();
24+
25+
expect(implementation.supportsImageSource(ImageSource.camera), false);
26+
});
27+
28+
test('supportsImageSource returns true for camera when there is a delegate',
29+
() async {
30+
final FakeCameraDelegatingImagePickerPlatform implementation =
31+
FakeCameraDelegatingImagePickerPlatform();
32+
implementation.cameraDelegate = FakeCameraDelegate();
33+
34+
expect(implementation.supportsImageSource(ImageSource.camera), true);
35+
});
36+
37+
test('getImageFromSource for camera throws if delegate is not set',
38+
() async {
39+
final FakeCameraDelegatingImagePickerPlatform implementation =
40+
FakeCameraDelegatingImagePickerPlatform();
41+
42+
await expectLater(
43+
implementation.getImageFromSource(source: ImageSource.camera),
44+
throwsStateError);
45+
});
46+
47+
test('getVideo for camera throws if delegate is not set', () async {
48+
final FakeCameraDelegatingImagePickerPlatform implementation =
49+
FakeCameraDelegatingImagePickerPlatform();
50+
51+
await expectLater(implementation.getVideo(source: ImageSource.camera),
52+
throwsStateError);
53+
});
54+
55+
test('getImageFromSource for camera calls delegate if set', () async {
56+
const String fakePath = '/tmp/foo';
57+
final FakeCameraDelegatingImagePickerPlatform implementation =
58+
FakeCameraDelegatingImagePickerPlatform();
59+
implementation.cameraDelegate =
60+
FakeCameraDelegate(result: XFile(fakePath));
61+
62+
expect(
63+
(await implementation.getImageFromSource(source: ImageSource.camera))!
64+
.path,
65+
fakePath);
66+
});
67+
68+
test('getVideo for camera calls delegate if set', () async {
69+
const String fakePath = '/tmp/foo';
70+
final FakeCameraDelegatingImagePickerPlatform implementation =
71+
FakeCameraDelegatingImagePickerPlatform();
72+
implementation.cameraDelegate =
73+
FakeCameraDelegate(result: XFile(fakePath));
74+
75+
expect((await implementation.getVideo(source: ImageSource.camera))!.path,
76+
fakePath);
77+
});
78+
});
79+
}
80+
81+
class FakeImagePickerPlatform extends ImagePickerPlatform {}
82+
83+
class FakeCameraDelegatingImagePickerPlatform
84+
extends CameraDelegatingImagePickerPlatform {}
85+
86+
class FakeCameraDelegate extends ImagePickerCameraDelegate {
87+
FakeCameraDelegate({this.result});
88+
89+
XFile? result;
90+
91+
@override
92+
Future<XFile?> takePhoto(
93+
{ImagePickerCameraDelegateOptions options =
94+
const ImagePickerCameraDelegateOptions()}) async {
95+
return result;
96+
}
97+
98+
@override
99+
Future<XFile?> takeVideo(
100+
{ImagePickerCameraDelegateOptions options =
101+
const ImagePickerCameraDelegateOptions()}) async {
102+
return result;
103+
}
104+
}

0 commit comments

Comments
 (0)