Skip to content

Commit

Permalink
[image_picker] Add limit parameter to pickMultiImage and pickMultiple…
Browse files Browse the repository at this point in the history
…Media to ios and Android (#6201)

Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` and supports its use on iOS and Android. The `limit` argument defines how many images or media files can be select.

Fixes: [flutter/flutter#85772](flutter/flutter#85772)
  • Loading branch information
pdenert authored Apr 16, 2024
1 parent 554f936 commit 43f1979
Show file tree
Hide file tree
Showing 35 changed files with 1,111 additions and 366 deletions.
7 changes: 7 additions & 0 deletions packages/image_picker/image_picker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.1.0

* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` which limits
the number of media that can be selected.
* Currently supported only on iOS and Android.
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.

## 1.0.8

* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1.
Expand Down
31 changes: 22 additions & 9 deletions packages/image_picker/image_picker/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class _MyHomePageState extends State<MyHomePage> {
final TextEditingController maxWidthController = TextEditingController();
final TextEditingController maxHeightController = TextEditingController();
final TextEditingController qualityController = TextEditingController();
final TextEditingController limitController = TextEditingController();

Future<void> _playVideo(XFile? file) async {
if (file != null && mounted) {
Expand Down Expand Up @@ -96,19 +97,21 @@ class _MyHomePageState extends State<MyHomePage> {
source: source, maxDuration: const Duration(seconds: 10));
await _playVideo(file);
} else if (isMultiImage) {
await _displayPickImageDialog(context,
(double? maxWidth, double? maxHeight, int? quality) async {
await _displayPickImageDialog(context, true, (double? maxWidth,
double? maxHeight, int? quality, int? limit) async {
try {
final List<XFile> pickedFileList = isMedia
? await _picker.pickMultipleMedia(
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: quality,
limit: limit,
)
: await _picker.pickMultiImage(
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: quality,
limit: limit,
);
setState(() {
_mediaFileList = pickedFileList;
Expand All @@ -120,8 +123,8 @@ class _MyHomePageState extends State<MyHomePage> {
}
});
} else if (isMedia) {
await _displayPickImageDialog(context,
(double? maxWidth, double? maxHeight, int? quality) async {
await _displayPickImageDialog(context, false, (double? maxWidth,
double? maxHeight, int? quality, int? limit) async {
try {
final List<XFile> pickedFileList = <XFile>[];
final XFile? media = await _picker.pickMedia(
Expand All @@ -142,8 +145,8 @@ class _MyHomePageState extends State<MyHomePage> {
}
});
} else {
await _displayPickImageDialog(context,
(double? maxWidth, double? maxHeight, int? quality) async {
await _displayPickImageDialog(context, false, (double? maxWidth,
double? maxHeight, int? quality, int? limit) async {
try {
final XFile? pickedFile = await _picker.pickImage(
source: source,
Expand Down Expand Up @@ -454,7 +457,7 @@ class _MyHomePageState extends State<MyHomePage> {
}

Future<void> _displayPickImageDialog(
BuildContext context, OnPickImageCallback onPick) async {
BuildContext context, bool isMulti, OnPickImageCallback onPick) async {
return showDialog(
context: context,
builder: (BuildContext context) {
Expand Down Expand Up @@ -483,6 +486,13 @@ class _MyHomePageState extends State<MyHomePage> {
decoration: const InputDecoration(
hintText: 'Enter quality if desired'),
),
if (isMulti)
TextField(
controller: limitController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: 'Enter limit if desired'),
),
],
),
actions: <Widget>[
Expand All @@ -504,7 +514,10 @@ class _MyHomePageState extends State<MyHomePage> {
final int? quality = qualityController.text.isNotEmpty
? int.parse(qualityController.text)
: null;
onPick(width, height, quality);
final int? limit = limitController.text.isNotEmpty
? int.parse(limitController.text)
: null;
onPick(width, height, quality, limit);
Navigator.of(context).pop();
}),
],
Expand All @@ -514,7 +527,7 @@ class _MyHomePageState extends State<MyHomePage> {
}

typedef OnPickImageCallback = void Function(
double? maxWidth, double? maxHeight, int? quality);
double? maxWidth, double? maxHeight, int? quality, int? limit);

class AspectRatioVideo extends StatefulWidget {
const AspectRatioVideo(this.controller, {super.key});
Expand Down
6 changes: 3 additions & 3 deletions packages/image_picker/image_picker/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ description: Demonstrates how to use the image_picker plugin.
publish_to: none

environment:
sdk: ^3.1.0
flutter: ">=3.13.0"
sdk: ^3.3.0
flutter: ">=3.19.0"

dependencies:
flutter:
Expand All @@ -17,7 +17,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
image_picker_platform_interface: ^2.8.0
image_picker_platform_interface: ^2.10.0
mime: ^1.0.4
video_player: ^2.7.0

Expand Down
10 changes: 7 additions & 3 deletions packages/image_picker/image_picker/lib/image_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class ImagePicker {
double? maxWidth,
double? maxHeight,
int? imageQuality,
int? limit,
bool requestFullMetadata = true,
}) {
final ImageOptions imageOptions = ImageOptions.createAndValidate(
Expand All @@ -138,8 +139,9 @@ class ImagePicker {
);

return platform.getMultiImageWithOptions(
options: MultiImagePickerOptions(
options: MultiImagePickerOptions.createAndValidate(
imageOptions: imageOptions,
limit: limit,
),
);
}
Expand Down Expand Up @@ -186,7 +188,7 @@ class ImagePicker {
bool requestFullMetadata = true,
}) async {
final List<XFile> listMedia = await platform.getMedia(
options: MediaOptions(
options: MediaOptions.createAndValidate(
imageOptions: ImageOptions.createAndValidate(
maxHeight: maxHeight,
maxWidth: maxWidth,
Expand Down Expand Up @@ -239,17 +241,19 @@ class ImagePicker {
double? maxWidth,
double? maxHeight,
int? imageQuality,
int? limit,
bool requestFullMetadata = true,
}) {
return platform.getMedia(
options: MediaOptions(
options: MediaOptions.createAndValidate(
allowMultiple: true,
imageOptions: ImageOptions.createAndValidate(
maxHeight: maxHeight,
maxWidth: maxWidth,
imageQuality: imageQuality,
requestFullMetadata: requestFullMetadata,
),
limit: limit,
),
);
}
Expand Down
10 changes: 5 additions & 5 deletions packages/image_picker/image_picker/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ description: Flutter plugin for selecting images from the Android and iOS image
library, and taking new pictures with the camera.
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
version: 1.0.8
version: 1.1.0

environment:
sdk: ^3.1.0
flutter: ">=3.13.0"
sdk: ^3.3.0
flutter: ">=3.19.0"

flutter:
plugin:
Expand All @@ -30,10 +30,10 @@ dependencies:
sdk: flutter
image_picker_android: ^0.8.7
image_picker_for_web: ">=2.2.0 <4.0.0"
image_picker_ios: ^0.8.9+1
image_picker_ios: ^0.8.8
image_picker_linux: ^0.2.1
image_picker_macos: ^0.2.1
image_picker_platform_interface: ^2.8.0
image_picker_platform_interface: ^2.10.0
image_picker_windows: ^0.2.1

dev_dependencies:
Expand Down
112 changes: 109 additions & 3 deletions packages/image_picker/image_picker/test/image_picker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,16 @@ void main() {
imageQuality: 70,
);
await picker.pickMultiImage(
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
maxWidth: 10.0,
maxHeight: 20.0,
imageQuality: 70,
);
await picker.pickMultiImage(
maxWidth: 10.0,
maxHeight: 20.0,
imageQuality: 70,
limit: 5,
);

verifyInOrder(<Object>[
mockPlatform.getMultiImageWithOptions(
Expand Down Expand Up @@ -529,6 +538,29 @@ void main() {
named: 'options',
),
),
mockPlatform.getMultiImageWithOptions(
options: argThat(
isInstanceOf<MultiImagePickerOptions>()
.having(
(MultiImagePickerOptions options) =>
options.imageOptions.maxWidth,
'maxWidth',
equals(10.0))
.having(
(MultiImagePickerOptions options) =>
options.imageOptions.maxWidth,
'maxHeight',
equals(10.0))
.having(
(MultiImagePickerOptions options) =>
options.imageOptions.imageQuality,
'imageQuality',
equals(70))
.having((MultiImagePickerOptions options) => options.limit,
'limit', equals(5)),
named: 'options',
),
),
]);
});

Expand All @@ -545,6 +577,24 @@ void main() {
);
});

test('does not accept a limit argument lower than 2', () {
final ImagePicker picker = ImagePicker();
expect(
() => picker.pickMultiImage(limit: -1),
throwsArgumentError,
);

expect(
() => picker.pickMultiImage(limit: 0),
throwsArgumentError,
);

expect(
() => picker.pickMultiImage(limit: 1),
throwsArgumentError,
);
});

test('handles an empty image file response gracefully', () async {
final ImagePicker picker = ImagePicker();

Expand Down Expand Up @@ -620,7 +670,15 @@ void main() {
imageQuality: 70,
);
await picker.pickMedia(
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
maxWidth: 10.0,
maxHeight: 20.0,
imageQuality: 70,
);
await picker.pickMedia(
maxWidth: 10.0,
maxHeight: 20.0,
imageQuality: 70,
);

verifyInOrder(<Object>[
mockPlatform.getMedia(
Expand Down Expand Up @@ -793,7 +851,16 @@ void main() {
imageQuality: 70,
);
await picker.pickMultipleMedia(
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
maxWidth: 10.0,
maxHeight: 20.0,
imageQuality: 70,
);
await picker.pickMultipleMedia(
maxWidth: 10.0,
maxHeight: 20.0,
imageQuality: 70,
limit: 5,
);

verifyInOrder(<Object>[
mockPlatform.getMedia(
Expand Down Expand Up @@ -885,6 +952,27 @@ void main() {
named: 'options',
),
),
mockPlatform.getMedia(
options: argThat(
isInstanceOf<MediaOptions>()
.having(
(MediaOptions options) => options.imageOptions.maxWidth,
'maxWidth',
equals(10.0))
.having(
(MediaOptions options) => options.imageOptions.maxWidth,
'maxHeight',
equals(10.0))
.having(
(MediaOptions options) =>
options.imageOptions.imageQuality,
'imageQuality',
equals(70))
.having((MediaOptions options) => options.limit, 'limit',
equals(5)),
named: 'options',
),
),
]);
});

Expand All @@ -901,6 +989,24 @@ void main() {
);
});

test('does not accept a limit argument lower than 2', () {
final ImagePicker picker = ImagePicker();
expect(
() => picker.pickMultipleMedia(limit: -1),
throwsArgumentError,
);

expect(
() => picker.pickMultipleMedia(limit: 0),
throwsArgumentError,
);

expect(
() => picker.pickMultipleMedia(limit: 1),
throwsArgumentError,
);
});

test('handles an empty image file response gracefully', () async {
final ImagePicker picker = ImagePicker();

Expand Down
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.10

* Adds limit parameter to `MediaOptions` and `MultiImagePickerOptions` that sets a limit to how many media or image items can be selected.

## 0.8.9+6

* Updates minSdkVersion to 19.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.flutter.plugins.imagepicker">

<application>
Expand All @@ -11,5 +12,15 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/flutter_image_picker_file_paths" />
</provider>
<!-- Trigger Google Play services to install the backported photo picker module. -->
<service android:name="com.google.android.gms.metadata.ModuleDependencies"
android:enabled="false"
android:exported="false"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
</intent-filter>
<meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>
</application>
</manifest>
Loading

0 comments on commit 43f1979

Please sign in to comment.