diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 7708c34ffe8c..0a238bcd51bf 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Introduce PickedFile type for the new API. + ## 1.0.1 * Update lower bound of dart dependency to 2.1.0. diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart index 4d960517b73b..71704b63ced4 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart @@ -19,6 +19,24 @@ class MethodChannelImagePicker extends ImagePickerPlatform { @visibleForTesting MethodChannel get channel => _channel; + @override + Future pickImage({ + @required ImageSource source, + double maxWidth, + double maxHeight, + int imageQuality, + CameraDevice preferredCameraDevice = CameraDevice.rear, + }) async { + String path = await pickImagePath( + source: source, + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + preferredCameraDevice: preferredCameraDevice, + ); + return path != null ? PickedFile(path) : null; + } + @override Future pickImagePath({ @required ImageSource source, @@ -53,6 +71,20 @@ class MethodChannelImagePicker extends ImagePickerPlatform { ); } + @override + Future pickVideo({ + @required ImageSource source, + CameraDevice preferredCameraDevice = CameraDevice.rear, + Duration maxDuration, + }) async { + String path = await pickVideoPath( + source: source, + maxDuration: maxDuration, + preferredCameraDevice: preferredCameraDevice, + ); + return path != null ? PickedFile(path) : null; + } + @override Future pickVideoPath({ @required ImageSource source, @@ -71,10 +103,48 @@ class MethodChannelImagePicker extends ImagePickerPlatform { } @override + Future retrieveLostData() async { + final Map result = + await _channel.invokeMapMethod('retrieve'); + + if (result == null) { + return LostData.empty(); + } + + assert(result.containsKey('path') ^ result.containsKey('errorCode')); + + final String type = result['type']; + assert(type == kTypeImage || type == kTypeVideo); + + RetrieveType retrieveType; + if (type == kTypeImage) { + retrieveType = RetrieveType.image; + } else if (type == kTypeVideo) { + retrieveType = RetrieveType.video; + } + + PlatformException exception; + if (result.containsKey('errorCode')) { + exception = PlatformException( + code: result['errorCode'], message: result['errorMessage']); + } + + final String path = result['path']; + + return LostData( + file: path != null ? PickedFile(path) : null, + exception: exception, + type: retrieveType, + ); + } + + @override + // ignore: deprecated_member_use_from_same_package Future retrieveLostDataAsDartIoFile() async { final Map result = await _channel.invokeMapMethod('retrieve'); if (result == null) { + // ignore: deprecated_member_use_from_same_package return LostDataResponse.empty(); } assert(result.containsKey('path') ^ result.containsKey('errorCode')); @@ -97,6 +167,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform { final String path = result['path']; + // ignore: deprecated_member_use_from_same_package return LostDataResponse( file: path == null ? null : File(path), exception: exception, 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 66e74dd95636..94be4c2f2ab1 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 @@ -60,6 +60,7 @@ abstract class ImagePickerPlatform extends PlatformInterface { /// /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost /// in this call. You can then call [retrieveLostDataAsDartIoFile] when your app relaunches to retrieve the lost data. + @Deprecated('Use pickImage instead.') Future pickImagePath({ @required ImageSource source, double maxWidth, @@ -84,6 +85,7 @@ abstract class ImagePickerPlatform extends PlatformInterface { /// /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost /// in this call. You can then call [retrieveLostDataAsDartIoFile] when your app relaunches to retrieve the lost data. + @Deprecated('Use pickVideo instead.') Future pickVideoPath({ @required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, @@ -92,7 +94,7 @@ abstract class ImagePickerPlatform extends PlatformInterface { throw UnimplementedError('pickVideoPath() has not been implemented.'); } - /// Retrieve the lost image file when [pickImage] or [pickVideo] failed because the MainActivity is destroyed. (Android only) + /// Retrieve the lost image file when [pickImagePath] or [pickVideoPath] failed because the MainActivity is destroyed. (Android only) /// /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive. /// Call this method to retrieve the lost data and process the data according to your APP's business logic. @@ -105,8 +107,81 @@ abstract class ImagePickerPlatform extends PlatformInterface { /// See also: /// * [LostDataResponse], for what's included in the response. /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction. + @Deprecated('Use retrieveLostData instead.') Future retrieveLostDataAsDartIoFile() { throw UnimplementedError( 'retrieveLostDataAsDartIoFile() has not been implemented.'); } + + // Next version of the API. + + /// Returns a [PickedFile] with the image that was picked. + /// + /// The `source` argument controls where the image comes from. This can + /// be either [ImageSource.camera] or [ImageSource.gallery]. + /// + /// If specified, the image will be at most `maxWidth` wide and + /// `maxHeight` tall. Otherwise the image will be returned at it's + /// original width and height. + /// + /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 + /// where 100 is the original/max quality. If `imageQuality` is null, the image with + /// the original quality will be returned. Compression is only supportted for certain + /// image types such as JPEG. If compression is not supported for the image that is picked, + /// an warning message will be logged. + /// + /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. + /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. + /// Defaults to [CameraDevice.rear]. + /// + /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost + /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. + Future pickImage({ + @required ImageSource source, + double maxWidth, + double maxHeight, + int imageQuality, + CameraDevice preferredCameraDevice = CameraDevice.rear, + }) { + throw UnimplementedError('pickImage() has not been implemented.'); + } + + /// Returns a [PickedFile] containing the video that was picked. + /// + /// The [source] argument controls where the video comes from. This can + /// be either [ImageSource.camera] or [ImageSource.gallery]. + /// + /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified, + /// the maximum duration will be infinite. + /// + /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. + /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. + /// Defaults to [CameraDevice.rear]. + /// + /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost + /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. + Future pickVideo({ + @required ImageSource source, + CameraDevice preferredCameraDevice = CameraDevice.rear, + Duration maxDuration, + }) { + throw UnimplementedError('pickVideo() has not been implemented.'); + } + + /// Retrieve the lost [PickedFile] file when [pickImage] or [pickVideo] failed because the MainActivity is destroyed. (Android only) + /// + /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive. + /// Call this method to retrieve the lost data and process the data according to your APP's business logic. + /// + /// Returns a [LostData] object if successfully retrieved the lost data. The [LostData] object can represent either a + /// successful image/video selection, or a failure. + /// + /// Calling this on a non-Android platform will throw [UnimplementedError] exception. + /// + /// See also: + /// * [LostData], for what's included in the response. + /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction. + Future retrieveLostData() { + throw UnimplementedError('retrieveLostData() has not been implemented.'); + } } diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/lost_data_response.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/lost_data_response.dart index 53e2decd123f..d82618b23cd1 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/types/lost_data_response.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/lost_data_response.dart @@ -12,6 +12,7 @@ import 'package:image_picker_platform_interface/src/types/types.dart'; /// Only applies to Android. /// See also: /// * [ImagePicker.retrieveLostData] for more details on retrieving lost data. +@Deprecated('Use methods that return a LostData object instead.') class LostDataResponse { /// Creates an instance with the given [file], [exception], and [type]. Any of /// the params may be null, but this is never considered to be empty. diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/base.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/base.dart new file mode 100644 index 000000000000..285294efcb3d --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/base.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:meta/meta.dart'; + +/// The interface for a PickedFile. +/// +/// A PickedFile is a container that wraps the path of a selected +/// file by the user and (in some platforms, like web) the bytes +/// with the contents of the file. +/// +/// This class is a very limited subset of dart:io [File], so all +/// the methods should seem familiar. +@immutable +abstract class PickedFileBase { + /// Construct a PickedFile + PickedFileBase(String path); + + /// Get the path of the picked file. + /// + /// This should only be used as a backwards-compatibility clutch + /// for mobile apps, or cosmetic reasons only (to show the user + /// the path they've picked). + /// + /// Accessing the data contained in the picked file by its path + /// is platform-dependant (and won't work on web), so use the + /// byte getters in the PickedFile instance instead. + String get path { + throw UnimplementedError('.path has not been implemented.'); + } + + /// Synchronously read the entire file contents as a string using the given [Encoding]. + /// + /// By default, `encoding` is [utf8]. + /// + /// Throws Exception if the operation fails. + Future readAsString({Encoding encoding = utf8}) { + throw UnimplementedError('readAsString() has not been implemented.'); + } + + /// Synchronously read the entire file contents as a list of bytes. + /// + /// Throws Exception if the operation fails. + Future readAsBytes() { + throw UnimplementedError('readAsBytes() has not been implemented.'); + } + + /// Create a new independent [Stream] for the contents of this file. + /// + /// If `start` is present, the file will be read from byte-offset `start`. Otherwise from the beginning (index 0). + /// + /// If `end` is present, only up to byte-index `end` will be read. Otherwise, until end of file. + /// + /// In order to make sure that system resources are freed, the stream must be read to completion or the subscription on the stream must be cancelled. + Stream openRead([int start, int end]) { + throw UnimplementedError('openRead() has not been implemented.'); + } +} diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart new file mode 100644 index 000000000000..0faf531f3f75 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http show readBytes; + +import './base.dart'; + +/// A PickedFile that works on web. +/// +/// It wraps the bytes of a selected file. +class PickedFile extends PickedFileBase { + final String path; + final Uint8List _initBytes; + + /// Construct a PickedFile object from its ObjectUrl. + /// + /// Optionally, this can be initialized with `bytes` + /// so no http requests are performed to retrieve files later. + PickedFile(this.path, {Uint8List bytes}) + : _initBytes = bytes, + super(path); + + Future get _bytes async { + if (_initBytes != null) { + return Future.value(UnmodifiableUint8ListView(_initBytes)); + } + return http.readBytes(path); + } + + @override + Future readAsString({Encoding encoding = utf8}) async { + return encoding.decode(await _bytes); + } + + @override + Future readAsBytes() async { + return Future.value(await _bytes); + } + + @override + Stream openRead([int start, int end]) async* { + final bytes = await _bytes; + yield bytes.sublist(start ?? 0, end ?? bytes.length); + } +} diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/io.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/io.dart new file mode 100644 index 000000000000..dd64558bf044 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/io.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import './base.dart'; + +/// A PickedFile backed by a dart:io File. +class PickedFile extends PickedFileBase { + final File _file; + + /// Construct a PickedFile object backed by a dart:io File. + PickedFile(String path) + : _file = File(path), + super(path); + + @override + String get path { + return _file.path; + } + + @override + Future readAsString({Encoding encoding = utf8}) { + return _file.readAsString(encoding: encoding); + } + + @override + Future readAsBytes() { + return _file.readAsBytes(); + } + + @override + Stream openRead([int start, int end]) { + return _file + .openRead(start ?? 0, end) + .map((chunk) => Uint8List.fromList(chunk)); + } +} diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/lost_data.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/lost_data.dart new file mode 100644 index 000000000000..b94e69de219e --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/lost_data.dart @@ -0,0 +1,49 @@ +// Copyright 2017 The Chromium 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/services.dart'; +import 'package:image_picker_platform_interface/src/types/types.dart'; + +/// The response object of [ImagePicker.retrieveLostData]. +/// +/// Only applies to Android. +/// See also: +/// * [ImagePicker.retrieveLostData] for more details on retrieving lost data. +class LostData { + /// Creates an instance with the given [file], [exception], and [type]. Any of + /// the params may be null, but this is never considered to be empty. + LostData({this.file, this.exception, this.type}); + + /// Initializes an instance with all member params set to null and considered + /// to be empty. + LostData.empty() + : file = null, + exception = null, + type = null, + _empty = true; + + /// Whether it is an empty response. + /// + /// An empty response should have [file], [exception] and [type] to be null. + bool get isEmpty => _empty; + + /// The file that was lost in a previous [pickImage] or [pickVideo] call due to MainActivity being destroyed. + /// + /// Can be null if [exception] exists. + final PickedFile file; + + /// The exception of the last [pickImage] or [pickVideo]. + /// + /// If the last [pickImage] or [pickVideo] threw some exception before the MainActivity destruction, this variable keeps that + /// exception. + /// You should handle this exception as if the [pickImage] or [pickVideo] got an exception when the MainActivity was not destroyed. + /// + /// Note that it is not the exception that caused the destruction of the MainActivity. + final PlatformException exception; + + /// Can either be [RetrieveType.image] or [RetrieveType.video]; + final RetrieveType type; + + bool _empty = false; +} diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/picked_file.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/picked_file.dart new file mode 100644 index 000000000000..b2a614ccb304 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/picked_file.dart @@ -0,0 +1,4 @@ +export 'lost_data.dart'; +export 'unsupported.dart' + if (dart.library.html) 'html.dart' + if (dart.library.io) 'io.dart'; diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/unsupported.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/unsupported.dart new file mode 100644 index 000000000000..bc10a4890c8d --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/unsupported.dart @@ -0,0 +1,14 @@ +import './base.dart'; + +/// A PickedFile is a cross-platform, simplified File abstraction. +/// +/// It wraps the bytes of a selected file, and its (platform-dependant) path. +class PickedFile extends PickedFileBase { + /// Construct a PickedFile object, from its `bytes`. + /// + /// Optionally, you may pass a `path`. See caveats in [PickedFileBase.path]. + PickedFile(String path) : super(path) { + throw UnimplementedError( + 'PickedFile is not available in your current platform.'); + } +} 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 98418109dc09..9c44fae1aa9d 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 @@ export 'camera_device.dart'; export 'image_source.dart'; export 'lost_data_response.dart'; export 'retrieve_type.dart'; +export 'picked_file/picked_file.dart'; /// Denotes that an image is being picked. const String kTypeImage = 'image'; diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index a4ea5d1f959a..946cf80c5187 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -3,12 +3,13 @@ description: A common platform interface for the image_picker plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_platform_interface # 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: 1.0.1 +version: 1.1.0 dependencies: flutter: sdk: flutter meta: ^1.1.8 + http: ^0.12.1 plugin_platform_interface: ^1.0.2 dev_dependencies: @@ -18,5 +19,5 @@ dev_dependencies: pedantic: ^1.8.0+1 environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.5.0 <3.0.0" flutter: ">=1.10.0 <2.0.0" diff --git a/packages/image_picker/image_picker_platform_interface/test/assets/hello.txt b/packages/image_picker/image_picker_platform_interface/test/assets/hello.txt new file mode 100644 index 000000000000..5dd01c177f5d --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/test/assets/hello.txt @@ -0,0 +1 @@ +Hello, world! \ No newline at end of file diff --git a/packages/image_picker/image_picker_platform_interface/test/method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/method_channel_image_picker_test.dart index 701379b84aae..ddaad3d32f41 100644 --- a/packages/image_picker/image_picker_platform_interface/test/method_channel_image_picker_test.dart +++ b/packages/image_picker/image_picker_platform_interface/test/method_channel_image_picker_test.dart @@ -299,6 +299,7 @@ void main() { 'path': '/example/path', }; }); + // ignore: deprecated_member_use_from_same_package final LostDataResponse response = await picker.retrieveLostDataAsDartIoFile(); expect(response.type, RetrieveType.image); @@ -313,6 +314,7 @@ void main() { 'errorMessage': 'test_error_message', }; }); + // ignore: deprecated_member_use_from_same_package final LostDataResponse response = await picker.retrieveLostDataAsDartIoFile(); expect(response.type, RetrieveType.video); @@ -338,6 +340,6 @@ void main() { }); expect(picker.retrieveLostDataAsDartIoFile(), throwsAssertionError); }); - }); + }, skip: isBrowser); }); } 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/new_method_channel_image_picker_test.dart new file mode 100644 index 000000000000..e7abe37e4838 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart @@ -0,0 +1,353 @@ +// Copyright 2019 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/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; +import 'package:image_picker_platform_interface/src/method_channel/method_channel_image_picker.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$MethodChannelImagePicker', () { + MethodChannelImagePicker picker = MethodChannelImagePicker(); + + final List log = []; + + setUp(() { + picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return ''; + }); + + log.clear(); + }); + + group('#pickImage', () { + test('passes the image source argument correctly', () async { + await picker.pickImage(source: ImageSource.camera); + await picker.pickImage(source: ImageSource.gallery); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 1, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + ], + ); + }); + + test('passes the width and height arguments correctly', () async { + await picker.pickImage(source: ImageSource.camera); + await picker.pickImage( + source: ImageSource.camera, + maxWidth: 10.0, + ); + await picker.pickImage( + source: ImageSource.camera, + maxHeight: 10.0, + ); + await picker.pickImage( + source: ImageSource.camera, + maxWidth: 10.0, + maxHeight: 20.0, + ); + await picker.pickImage( + source: ImageSource.camera, + maxWidth: 10.0, + imageQuality: 70, + ); + await picker.pickImage( + source: ImageSource.camera, + maxHeight: 10.0, + imageQuality: 70, + ); + await picker.pickImage( + source: ImageSource.camera, + maxWidth: 10.0, + maxHeight: 20.0, + imageQuality: 70, + ); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': 70, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': 70, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': 70, + 'cameraDevice': 0 + }), + ], + ); + }); + + test('does not accept a negative width or height argument', () { + expect( + () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0), + throwsArgumentError, + ); + + expect( + () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0), + throwsArgumentError, + ); + }); + + test('handles a null image path response gracefully', () async { + picker.channel + .setMockMethodCallHandler((MethodCall methodCall) => null); + + expect(await picker.pickImage(source: ImageSource.gallery), isNull); + expect(await picker.pickImage(source: ImageSource.camera), isNull); + }); + + test('camera position defaults to back', () async { + await picker.pickImage(source: ImageSource.camera); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0, + }), + ], + ); + }); + + test('camera position can set to front', () async { + await picker.pickImage( + source: ImageSource.camera, + preferredCameraDevice: CameraDevice.front); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 1, + }), + ], + ); + }); + }); + + group('#pickVideoPath', () { + test('passes the image source argument correctly', () async { + await picker.pickVideo(source: ImageSource.camera); + await picker.pickVideo(source: ImageSource.gallery); + + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'cameraDevice': 0, + 'maxDuration': null, + }), + isMethodCall('pickVideo', arguments: { + 'source': 1, + 'cameraDevice': 0, + 'maxDuration': null, + }), + ], + ); + }); + + test('passes the duration argument correctly', () async { + await picker.pickVideo(source: ImageSource.camera); + await picker.pickVideo( + source: ImageSource.camera, + maxDuration: const Duration(seconds: 10), + ); + await picker.pickVideo( + source: ImageSource.camera, + maxDuration: const Duration(minutes: 1), + ); + await picker.pickVideo( + source: ImageSource.camera, + maxDuration: const Duration(hours: 1), + ); + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': null, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 10, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 60, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 3600, + 'cameraDevice': 0, + }), + ], + ); + }); + + test('handles a null video path response gracefully', () async { + picker.channel + .setMockMethodCallHandler((MethodCall methodCall) => null); + + expect(await picker.pickVideo(source: ImageSource.gallery), isNull); + expect(await picker.pickVideo(source: ImageSource.camera), isNull); + }); + + test('camera position defaults to back', () async { + await picker.pickVideo(source: ImageSource.camera); + + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'cameraDevice': 0, + 'maxDuration': null, + }), + ], + ); + }); + + test('camera position can set to front', () async { + await picker.pickVideo( + source: ImageSource.camera, + preferredCameraDevice: CameraDevice.front, + ); + + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': null, + 'cameraDevice': 1, + }), + ], + ); + }); + }); + + group('#retrieveLostData', () { + test('retrieveLostData get success response', () async { + picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + return { + 'type': 'image', + 'path': '/example/path', + }; + }); + // ignore: deprecated_member_use_from_same_package + final LostData response = await picker.retrieveLostData(); + expect(response.type, RetrieveType.image); + expect(response.file.path, '/example/path'); + }); + + test('retrieveLostData get error response', () async { + picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + return { + 'type': 'video', + 'errorCode': 'test_error_code', + 'errorMessage': 'test_error_message', + }; + }); + // ignore: deprecated_member_use_from_same_package + final LostData response = await picker.retrieveLostData(); + expect(response.type, RetrieveType.video); + expect(response.exception.code, 'test_error_code'); + expect(response.exception.message, 'test_error_message'); + }); + + test('retrieveLostData get null response', () async { + picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + return null; + }); + expect((await picker.retrieveLostData()).isEmpty, true); + }); + + test('retrieveLostData get both path and error should throw', () async { + picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + return { + 'type': 'video', + 'errorCode': 'test_error_code', + 'errorMessage': 'test_error_message', + 'path': '/example/path', + }; + }); + expect(picker.retrieveLostData(), throwsAssertionError); + }); + }); + }); +} diff --git a/packages/image_picker/image_picker_platform_interface/test/picked_file_html_test.dart b/packages/image_picker/image_picker_platform_interface/test/picked_file_html_test.dart new file mode 100644 index 000000000000..49d84ff88f88 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/test/picked_file_html_test.dart @@ -0,0 +1,39 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('chrome') // Uses web-only Flutter SDK + +import 'dart:convert'; +import 'dart:html' as html; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; + +final String expectedStringContents = 'Hello, world!'; +final Uint8List bytes = utf8.encode(expectedStringContents); +final html.File textFile = html.File([bytes], 'hello.txt'); +final String textFileUrl = html.Url.createObjectUrl(textFile); + +void main() { + group('Create with an objectUrl', () { + final pickedFile = PickedFile(textFileUrl); + + test('Can be read as a string', () async { + expect(await pickedFile.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await pickedFile.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await pickedFile.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect( + await pickedFile.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); +} diff --git a/packages/image_picker/image_picker_platform_interface/test/picked_file_io_test.dart b/packages/image_picker/image_picker_platform_interface/test/picked_file_io_test.dart new file mode 100644 index 000000000000..94ff759a2fb2 --- /dev/null +++ b/packages/image_picker/image_picker_platform_interface/test/picked_file_io_test.dart @@ -0,0 +1,39 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('vm') // Uses dart:io + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; + +final String expectedStringContents = 'Hello, world!'; +final Uint8List bytes = utf8.encode(expectedStringContents); +final File textFile = File('./assets/hello.txt'); +final String textFilePath = textFile.path; + +void main() { + group('Create with an objectUrl', () { + final pickedFile = PickedFile(textFilePath); + + test('Can be read as a string', () async { + expect(await pickedFile.readAsString(), equals(expectedStringContents)); + }); + test('Can be read as bytes', () async { + expect(await pickedFile.readAsBytes(), equals(bytes)); + }); + + test('Can be read as a stream', () async { + expect(await pickedFile.openRead().first, equals(bytes)); + }); + + test('Stream can be sliced', () async { + expect( + await pickedFile.openRead(2, 5).first, equals(bytes.sublist(2, 5))); + }); + }); +}