Toggle navigation
Toggle navigation
This project
Loading...
Sign in
flutter_package
/
image_picker
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
顾海波
2025-05-19 20:47:18 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
1651f6b660338a0215f6001a787bbaa91ddc1d5f
1651f6b6
1 parent
c889d73c
【需求】适配鸿蒙,安卓数量选择
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
578 additions
and
172 deletions
image_picker/lib/image_picker.dart
image_picker_android/android/build.gradle
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
image_picker_android/lib/image_picker_android.dart
image_picker_android/lib/src/messages.g.dart
image_picker_ohos/lib/image_picker_ohos.dart
image_picker_ohos/lib/src/messages.g.dart
image_picker_ohos/ohos/src/main/ets/image_picker/ImagePickerDelegate.ets
image_picker_ohos/ohos/src/main/ets/image_picker/ImagePickerPlugin.ets
image_picker_ohos/ohos/src/main/ets/image_picker/Messages.ets
image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
image_picker_platform_interface/lib/src/types/media_options.dart
image_picker_platform_interface/lib/src/types/multi_image_picker_options.dart
image_picker/lib/image_picker.dart
View file @
1651f6b
...
...
@@ -112,6 +112,9 @@ class ImagePicker {
/// image types such as JPEG and on Android PNG and WebP, too. If compression is not
/// supported for the image that is picked, a warning message will be logged.
///
/// The `limit` parameter modifies the maximum number of images that can be selected.
/// This value may be ignored by platforms that cannot support it.
///
/// Use `requestFullMetadata` (defaults to `true`) to control how much additional
/// information the plugin tries to get.
/// If `requestFullMetadata` is set to `true`, the plugin tries to get the full
...
...
@@ -128,6 +131,7 @@ class ImagePicker {
double
?
maxWidth
,
double
?
maxHeight
,
int
?
imageQuality
,
int
?
limit
,
bool
requestFullMetadata
=
true
,
})
{
final
ImageOptions
imageOptions
=
ImageOptions
.
createAndValidate
(
...
...
@@ -138,8 +142,9 @@ class ImagePicker {
);
return
platform
.
getMultiImageWithOptions
(
options:
MultiImagePickerOptions
(
options:
MultiImagePickerOptions
.
createAndValidate
(
imageOptions:
imageOptions
,
limit:
limit
,
),
);
}
...
...
@@ -186,7 +191,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
,
...
...
@@ -223,6 +228,9 @@ class ImagePicker {
/// image types such as JPEG and on Android PNG and WebP, too. If compression is not
/// supported for the image that is picked, a warning message will be logged.
///
/// The `limit` parameter modifies the maximum number of media that can be selected.
/// This value may be ignored by platforms that cannot support it.
///
/// Use `requestFullMetadata` (defaults to `true`) to control how much additional
/// information the plugin tries to get.
/// If `requestFullMetadata` is set to `true`, the plugin tries to get the full
...
...
@@ -239,10 +247,11 @@ 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
,
...
...
@@ -250,6 +259,7 @@ class ImagePicker {
imageQuality:
imageQuality
,
requestFullMetadata:
requestFullMetadata
,
),
limit:
limit
,
),
);
}
...
...
image_picker_android/android/build.gradle
View file @
1651f6b
...
...
@@ -8,7 +8,7 @@ buildscript {
}
dependencies
{
classpath
'com.android.tools.build:gradle:
7.2
.1'
classpath
'com.android.tools.build:gradle:
8.5
.1'
}
}
...
...
@@ -26,22 +26,22 @@ android {
if
(
project
.
android
.
hasProperty
(
"namespace"
))
{
namespace
'io.flutter.plugins.imagepicker'
}
compileSdk
Version
33
compileSdk
34
defaultConfig
{
minSdkVersion
1
6
minSdkVersion
1
9
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
}
lintOptions
{
checkAllWarnings
true
warningsAsErrors
true
disable
'AndroidGradlePluginVersion'
,
'InvalidPackage'
,
'GradleDependency'
disable
'AndroidGradlePluginVersion'
,
'InvalidPackage'
,
'GradleDependency'
,
'NewerVersionAvailable'
}
dependencies
{
implementation
'androidx.core:core:1.10.1'
implementation
'androidx.annotation:annotation:1.3.0'
implementation
'androidx.exifinterface:exifinterface:1.3.6'
implementation
'androidx.activity:activity:1.7.2'
implementation
'androidx.core:core:1.13.1'
implementation
'androidx.annotation:annotation:1.8.2'
implementation
'androidx.exifinterface:exifinterface:1.3.7'
implementation
'androidx.activity:activity:1.9.1'
// org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions.
// See: https://youtrack.jetbrains.com/issue/KT-55297/kotlin-stdlib-should-declare-constraints-on-kotlin-stdlib-jdk8-and-kotlin-stdlib-jdk7
implementation
(
platform
(
"org.jetbrains.kotlin:kotlin-bom:1.8.22"
))
...
...
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java
View file @
1651f6b
...
...
@@ -4,47 +4,138 @@
package
io
.
flutter
.
plugins
.
imagepicker
;
import
android.util.Log
;
import
androidx.exifinterface.media.ExifInterface
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.List
;
class
ExifDataCopier
{
void
copyExif
(
String
filePathOri
,
String
filePathDest
)
{
try
{
ExifInterface
oldExif
=
new
ExifInterface
(
filePathOri
);
ExifInterface
newExif
=
new
ExifInterface
(
filePathDest
);
/**
* Copies all exif data not related to image structure and orientation tag. Data not related to
* image structure consists of category II (Shooting condition related metadata) and category III
* (Metadata storing other information) tags. Category I tags are not copied because they may be
* invalidated as a result of resizing. The exception is the orientation tag which is known to not
* be invalidated and is crucial for proper display of the image.
*
* <p>The categories mentioned refer to standard "CIPA DC-008-Translation-2012 Exchangeable image
* file format for digital still cameras: Exif Version 2.3"
* https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf. Version 2.3 has been chosen because
* {@code ExifInterface} is based on it.
*/
void
copyExif
(
ExifInterface
oldExif
,
ExifInterface
newExif
)
throws
IOException
{
@SuppressWarnings
(
"deprecation"
)
List
<
String
>
attributes
=
Arrays
.
asList
(
"FNumber"
,
"ExposureTime"
,
"ISOSpeedRatings"
,
"GPSAltitude"
,
"GPSAltitudeRef"
,
"FocalLength"
,
"GPSDateStamp"
,
"WhiteBalance"
,
"GPSProcessingMethod"
,
"GPSTimeStamp"
,
"DateTime"
,
"Flash"
,
"GPSLatitude"
,
"GPSLatitudeRef"
,
"GPSLongitude"
,
"GPSLongitudeRef"
,
"Make"
,
"Model"
,
"Orientation"
);
ExifInterface
.
TAG_IMAGE_DESCRIPTION
,
ExifInterface
.
TAG_MAKE
,
ExifInterface
.
TAG_MODEL
,
ExifInterface
.
TAG_SOFTWARE
,
ExifInterface
.
TAG_DATETIME
,
ExifInterface
.
TAG_ARTIST
,
ExifInterface
.
TAG_COPYRIGHT
,
ExifInterface
.
TAG_EXPOSURE_TIME
,
ExifInterface
.
TAG_F_NUMBER
,
ExifInterface
.
TAG_EXPOSURE_PROGRAM
,
ExifInterface
.
TAG_SPECTRAL_SENSITIVITY
,
ExifInterface
.
TAG_PHOTOGRAPHIC_SENSITIVITY
,
ExifInterface
.
TAG_ISO_SPEED_RATINGS
,
ExifInterface
.
TAG_OECF
,
ExifInterface
.
TAG_SENSITIVITY_TYPE
,
ExifInterface
.
TAG_STANDARD_OUTPUT_SENSITIVITY
,
ExifInterface
.
TAG_RECOMMENDED_EXPOSURE_INDEX
,
ExifInterface
.
TAG_ISO_SPEED
,
ExifInterface
.
TAG_ISO_SPEED_LATITUDE_YYY
,
ExifInterface
.
TAG_ISO_SPEED_LATITUDE_ZZZ
,
ExifInterface
.
TAG_EXIF_VERSION
,
ExifInterface
.
TAG_DATETIME_ORIGINAL
,
ExifInterface
.
TAG_DATETIME_DIGITIZED
,
ExifInterface
.
TAG_OFFSET_TIME
,
ExifInterface
.
TAG_OFFSET_TIME_ORIGINAL
,
ExifInterface
.
TAG_OFFSET_TIME_DIGITIZED
,
ExifInterface
.
TAG_SHUTTER_SPEED_VALUE
,
ExifInterface
.
TAG_APERTURE_VALUE
,
ExifInterface
.
TAG_BRIGHTNESS_VALUE
,
ExifInterface
.
TAG_EXPOSURE_BIAS_VALUE
,
ExifInterface
.
TAG_MAX_APERTURE_VALUE
,
ExifInterface
.
TAG_SUBJECT_DISTANCE
,
ExifInterface
.
TAG_METERING_MODE
,
ExifInterface
.
TAG_LIGHT_SOURCE
,
ExifInterface
.
TAG_FLASH
,
ExifInterface
.
TAG_FOCAL_LENGTH
,
ExifInterface
.
TAG_MAKER_NOTE
,
ExifInterface
.
TAG_USER_COMMENT
,
ExifInterface
.
TAG_SUBSEC_TIME
,
ExifInterface
.
TAG_SUBSEC_TIME_ORIGINAL
,
ExifInterface
.
TAG_SUBSEC_TIME_DIGITIZED
,
ExifInterface
.
TAG_FLASHPIX_VERSION
,
ExifInterface
.
TAG_FLASH_ENERGY
,
ExifInterface
.
TAG_SPATIAL_FREQUENCY_RESPONSE
,
ExifInterface
.
TAG_FOCAL_PLANE_X_RESOLUTION
,
ExifInterface
.
TAG_FOCAL_PLANE_Y_RESOLUTION
,
ExifInterface
.
TAG_FOCAL_PLANE_RESOLUTION_UNIT
,
ExifInterface
.
TAG_EXPOSURE_INDEX
,
ExifInterface
.
TAG_SENSING_METHOD
,
ExifInterface
.
TAG_FILE_SOURCE
,
ExifInterface
.
TAG_SCENE_TYPE
,
ExifInterface
.
TAG_CFA_PATTERN
,
ExifInterface
.
TAG_CUSTOM_RENDERED
,
ExifInterface
.
TAG_EXPOSURE_MODE
,
ExifInterface
.
TAG_WHITE_BALANCE
,
ExifInterface
.
TAG_DIGITAL_ZOOM_RATIO
,
ExifInterface
.
TAG_FOCAL_LENGTH_IN_35MM_FILM
,
ExifInterface
.
TAG_SCENE_CAPTURE_TYPE
,
ExifInterface
.
TAG_GAIN_CONTROL
,
ExifInterface
.
TAG_CONTRAST
,
ExifInterface
.
TAG_SATURATION
,
ExifInterface
.
TAG_SHARPNESS
,
ExifInterface
.
TAG_DEVICE_SETTING_DESCRIPTION
,
ExifInterface
.
TAG_SUBJECT_DISTANCE_RANGE
,
ExifInterface
.
TAG_IMAGE_UNIQUE_ID
,
ExifInterface
.
TAG_CAMERA_OWNER_NAME
,
ExifInterface
.
TAG_BODY_SERIAL_NUMBER
,
ExifInterface
.
TAG_LENS_SPECIFICATION
,
ExifInterface
.
TAG_LENS_MAKE
,
ExifInterface
.
TAG_LENS_MODEL
,
ExifInterface
.
TAG_LENS_SERIAL_NUMBER
,
ExifInterface
.
TAG_GPS_VERSION_ID
,
ExifInterface
.
TAG_GPS_LATITUDE_REF
,
ExifInterface
.
TAG_GPS_LATITUDE
,
ExifInterface
.
TAG_GPS_LONGITUDE_REF
,
ExifInterface
.
TAG_GPS_LONGITUDE
,
ExifInterface
.
TAG_GPS_ALTITUDE_REF
,
ExifInterface
.
TAG_GPS_ALTITUDE
,
ExifInterface
.
TAG_GPS_TIMESTAMP
,
ExifInterface
.
TAG_GPS_SATELLITES
,
ExifInterface
.
TAG_GPS_STATUS
,
ExifInterface
.
TAG_GPS_MEASURE_MODE
,
ExifInterface
.
TAG_GPS_DOP
,
ExifInterface
.
TAG_GPS_SPEED_REF
,
ExifInterface
.
TAG_GPS_SPEED
,
ExifInterface
.
TAG_GPS_TRACK_REF
,
ExifInterface
.
TAG_GPS_TRACK
,
ExifInterface
.
TAG_GPS_IMG_DIRECTION_REF
,
ExifInterface
.
TAG_GPS_IMG_DIRECTION
,
ExifInterface
.
TAG_GPS_MAP_DATUM
,
ExifInterface
.
TAG_GPS_DEST_LATITUDE_REF
,
ExifInterface
.
TAG_GPS_DEST_LATITUDE
,
ExifInterface
.
TAG_GPS_DEST_LONGITUDE_REF
,
ExifInterface
.
TAG_GPS_DEST_LONGITUDE
,
ExifInterface
.
TAG_GPS_DEST_BEARING_REF
,
ExifInterface
.
TAG_GPS_DEST_BEARING
,
ExifInterface
.
TAG_GPS_DEST_DISTANCE_REF
,
ExifInterface
.
TAG_GPS_DEST_DISTANCE
,
ExifInterface
.
TAG_GPS_PROCESSING_METHOD
,
ExifInterface
.
TAG_GPS_AREA_INFORMATION
,
ExifInterface
.
TAG_GPS_DATESTAMP
,
ExifInterface
.
TAG_GPS_DIFFERENTIAL
,
ExifInterface
.
TAG_GPS_H_POSITIONING_ERROR
,
ExifInterface
.
TAG_INTEROPERABILITY_INDEX
,
ExifInterface
.
TAG_ORIENTATION
);
for
(
String
attribute
:
attributes
)
{
setIfNotNull
(
oldExif
,
newExif
,
attribute
);
}
newExif
.
saveAttributes
();
}
catch
(
Exception
ex
)
{
Log
.
e
(
"ExifDataCopier"
,
"Error preserving Exif data on selected image: "
+
ex
);
}
}
private
static
void
setIfNotNull
(
ExifInterface
oldExif
,
ExifInterface
newExif
,
String
property
)
{
...
...
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java
View file @
1651f6b
...
...
@@ -7,6 +7,7 @@ package io.flutter.plugins.imagepicker;
import
android.Manifest
;
import
android.app.Activity
;
import
android.content.ActivityNotFoundException
;
import
android.content.ClipData
;
import
android.content.Intent
;
import
android.content.pm.PackageManager
;
import
android.content.pm.ResolveInfo
;
...
...
@@ -294,10 +295,12 @@ public class ImagePickerDelegate
private
void
launchPickMediaFromGalleryIntent
(
Messages
.
GeneralOptions
generalOptions
)
{
Intent
pickMediaIntent
;
if
(
generalOptions
.
getUsePhotoPicker
()
&&
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
KITKAT
)
{
if
(
generalOptions
.
getUsePhotoPicker
())
{
if
(
generalOptions
.
getAllowMultiple
())
{
int
limit
=
ImagePickerUtils
.
getLimitFromOption
(
generalOptions
);
pickMediaIntent
=
new
ActivityResultContracts
.
PickMultipleVisualMedia
()
new
ActivityResultContracts
.
PickMultipleVisualMedia
(
limit
)
.
createIntent
(
activity
,
new
PickVisualMediaRequest
.
Builder
()
...
...
@@ -319,10 +322,8 @@ public class ImagePickerDelegate
pickMediaIntent
.
setType
(
"*/*"
);
String
[]
mimeTypes
=
{
"video/*"
,
"image/*"
};
pickMediaIntent
.
putExtra
(
"CONTENT_TYPE"
,
mimeTypes
);
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
JELLY_BEAN_MR2
)
{
pickMediaIntent
.
putExtra
(
Intent
.
EXTRA_ALLOW_MULTIPLE
,
generalOptions
.
getAllowMultiple
());
}
}
activity
.
startActivityForResult
(
pickMediaIntent
,
REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY
);
}
...
...
@@ -340,7 +341,7 @@ public class ImagePickerDelegate
private
void
launchPickVideoFromGalleryIntent
(
Boolean
usePhotoPicker
)
{
Intent
pickVideoIntent
;
if
(
usePhotoPicker
&&
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
KITKAT
)
{
if
(
usePhotoPicker
)
{
pickVideoIntent
=
new
ActivityResultContracts
.
PickVisualMedia
()
.
createIntent
(
...
...
@@ -403,7 +404,7 @@ public class ImagePickerDelegate
}
catch
(
ActivityNotFoundException
e
)
{
try
{
// If we can't delete the file again here, there's not really anything we can do about it.
//noinspection ResultOfMethodCallIgnored
//
noinspection ResultOfMethodCallIgnored
videoFile
.
delete
();
}
catch
(
SecurityException
exception
)
{
exception
.
printStackTrace
();
...
...
@@ -427,18 +428,19 @@ public class ImagePickerDelegate
public
void
chooseMultiImageFromGallery
(
@NonNull
ImageSelectionOptions
options
,
boolean
usePhotoPicker
,
int
limit
,
@NonNull
Messages
.
Result
<
List
<
String
>>
result
)
{
if
(!
setPendingOptionsAndResult
(
options
,
null
,
result
))
{
finishWithAlreadyActiveError
(
result
);
return
;
}
launchMultiPickImageFromGalleryIntent
(
usePhotoPicker
);
launchMultiPickImageFromGalleryIntent
(
usePhotoPicker
,
limit
);
}
private
void
launchPickImageFromGalleryIntent
(
Boolean
usePhotoPicker
)
{
Intent
pickImageIntent
;
if
(
usePhotoPicker
&&
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
KITKAT
)
{
if
(
usePhotoPicker
)
{
pickImageIntent
=
new
ActivityResultContracts
.
PickVisualMedia
()
.
createIntent
(
...
...
@@ -453,11 +455,11 @@ public class ImagePickerDelegate
activity
.
startActivityForResult
(
pickImageIntent
,
REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY
);
}
private
void
launchMultiPickImageFromGalleryIntent
(
Boolean
usePhotoPicker
)
{
private
void
launchMultiPickImageFromGalleryIntent
(
Boolean
usePhotoPicker
,
int
limit
)
{
Intent
pickMultiImageIntent
;
if
(
usePhotoPicker
&&
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
KITKAT
)
{
if
(
usePhotoPicker
)
{
pickMultiImageIntent
=
new
ActivityResultContracts
.
PickMultipleVisualMedia
()
new
ActivityResultContracts
.
PickMultipleVisualMedia
(
limit
)
.
createIntent
(
activity
,
new
PickVisualMediaRequest
.
Builder
()
...
...
@@ -466,10 +468,8 @@ public class ImagePickerDelegate
}
else
{
pickMultiImageIntent
=
new
Intent
(
Intent
.
ACTION_GET_CONTENT
);
pickMultiImageIntent
.
setType
(
"image/*"
);
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
JELLY_BEAN_MR2
)
{
pickMultiImageIntent
.
putExtra
(
Intent
.
EXTRA_ALLOW_MULTIPLE
,
true
);
}
}
activity
.
startActivityForResult
(
pickMultiImageIntent
,
REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY
);
}
...
...
@@ -515,7 +515,7 @@ public class ImagePickerDelegate
}
catch
(
ActivityNotFoundException
e
)
{
try
{
// If we can't delete the file again here, there's not really anything we can do about it.
//noinspection ResultOfMethodCallIgnored
//
noinspection ResultOfMethodCallIgnored
imageFile
.
delete
();
}
catch
(
SecurityException
exception
)
{
exception
.
printStackTrace
();
...
...
@@ -549,10 +549,14 @@ public class ImagePickerDelegate
private
void
grantUriPermissions
(
Intent
intent
,
Uri
imageUri
)
{
PackageManager
packageManager
=
activity
.
getPackageManager
();
// TODO(stuartmorgan): Add new codepath: https://github.com/flutter/flutter/issues/121816
@SuppressWarnings
(
"deprecation"
)
List
<
ResolveInfo
>
compatibleActivities
=
packageManager
.
queryIntentActivities
(
intent
,
PackageManager
.
MATCH_DEFAULT_ONLY
);
List
<
ResolveInfo
>
compatibleActivities
;
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
TIRAMISU
)
{
compatibleActivities
=
packageManager
.
queryIntentActivities
(
intent
,
PackageManager
.
ResolveInfoFlags
.
of
(
PackageManager
.
MATCH_DEFAULT_ONLY
));
}
else
{
compatibleActivities
=
queryIntentActivitiesPreApi33
(
packageManager
,
intent
);
}
for
(
ResolveInfo
info
:
compatibleActivities
)
{
activity
.
grantUriPermission
(
...
...
@@ -562,6 +566,12 @@ public class ImagePickerDelegate
}
}
@SuppressWarnings
(
"deprecation"
)
private
static
List
<
ResolveInfo
>
queryIntentActivitiesPreApi33
(
PackageManager
packageManager
,
Intent
intent
)
{
return
packageManager
.
queryIntentActivities
(
intent
,
PackageManager
.
MATCH_DEFAULT_ONLY
);
}
@Override
public
boolean
onRequestPermissionsResult
(
int
requestCode
,
@NonNull
String
[]
permissions
,
@NonNull
int
[]
grantResults
)
{
...
...
@@ -628,10 +638,57 @@ public class ImagePickerDelegate
return
true
;
}
@Nullable
private
ArrayList
<
MediaPath
>
getPathsFromIntent
(
@NonNull
Intent
data
,
boolean
includeMimeType
)
{
ArrayList
<
MediaPath
>
paths
=
new
ArrayList
<>();
Uri
uri
=
data
.
getData
();
// On several pre-Android 13 devices using Android Photo Picker, the Uri from getData() could
// be null.
if
(
uri
==
null
)
{
ClipData
clipData
=
data
.
getClipData
();
// If data.getData() and data.getClipData() are both null, we are in an error state. By
// convention we return null from here, and then finish with an error from the corresponding
// handler.
if
(
clipData
==
null
)
{
return
null
;
}
for
(
int
i
=
0
;
i
<
data
.
getClipData
().
getItemCount
();
i
++)
{
uri
=
data
.
getClipData
().
getItemAt
(
i
).
getUri
();
// Same error state as above.
if
(
uri
==
null
)
{
return
null
;
}
String
path
=
fileUtils
.
getPathFromUri
(
activity
,
uri
);
// Again, same error state as above.
if
(
path
==
null
)
{
return
null
;
}
String
mimeType
=
includeMimeType
?
activity
.
getContentResolver
().
getType
(
uri
)
:
null
;
paths
.
add
(
new
MediaPath
(
path
,
mimeType
));
}
}
else
{
String
path
=
fileUtils
.
getPathFromUri
(
activity
,
uri
);
if
(
path
==
null
)
{
return
null
;
}
paths
.
add
(
new
MediaPath
(
path
,
null
));
}
return
paths
;
}
private
void
handleChooseImageResult
(
int
resultCode
,
Intent
data
)
{
if
(
resultCode
==
Activity
.
RESULT_OK
&&
data
!=
null
)
{
String
path
=
fileUtils
.
getPathFromUri
(
activity
,
data
.
getData
());
handleImageResult
(
path
,
false
);
ArrayList
<
MediaPath
>
paths
=
getPathsFromIntent
(
data
,
false
);
// If there's no valid Uri, return an error
if
(
paths
==
null
)
{
finishWithError
(
"no_valid_image_uri"
,
"Cannot find the selected image."
);
return
;
}
handleMediaResult
(
paths
);
return
;
}
...
...
@@ -659,17 +716,13 @@ public class ImagePickerDelegate
private
void
handleChooseMediaResult
(
int
resultCode
,
Intent
intent
)
{
if
(
resultCode
==
Activity
.
RESULT_OK
&&
intent
!=
null
)
{
ArrayList
<
MediaPath
>
paths
=
new
ArrayList
<>();
if
(
intent
.
getClipData
()
!=
null
)
{
for
(
int
i
=
0
;
i
<
intent
.
getClipData
().
getItemCount
();
i
++)
{
Uri
uri
=
intent
.
getClipData
().
getItemAt
(
i
).
getUri
();
String
path
=
fileUtils
.
getPathFromUri
(
activity
,
uri
);
String
mimeType
=
activity
.
getContentResolver
().
getType
(
uri
);
paths
.
add
(
new
MediaPath
(
path
,
mimeType
));
}
}
else
{
paths
.
add
(
new
MediaPath
(
fileUtils
.
getPathFromUri
(
activity
,
intent
.
getData
()),
null
));
ArrayList
<
MediaPath
>
paths
=
getPathsFromIntent
(
intent
,
true
);
// If there's no valid Uri, return an error
if
(
paths
==
null
)
{
finishWithError
(
"no_valid_media_uri"
,
"Cannot find the selected media."
);
return
;
}
handleMediaResult
(
paths
);
return
;
}
...
...
@@ -680,17 +733,14 @@ public class ImagePickerDelegate
private
void
handleChooseMultiImageResult
(
int
resultCode
,
Intent
intent
)
{
if
(
resultCode
==
Activity
.
RESULT_OK
&&
intent
!=
null
)
{
ArrayList
<
MediaPath
>
paths
=
new
ArrayList
<>();
if
(
intent
.
getClipData
()
!=
null
)
{
for
(
int
i
=
0
;
i
<
intent
.
getClipData
().
getItemCount
();
i
++)
{
paths
.
add
(
new
MediaPath
(
fileUtils
.
getPathFromUri
(
activity
,
intent
.
getClipData
().
getItemAt
(
i
).
getUri
()),
null
));
}
}
else
{
paths
.
add
(
new
MediaPath
(
fileUtils
.
getPathFromUri
(
activity
,
intent
.
getData
()),
null
));
ArrayList
<
MediaPath
>
paths
=
getPathsFromIntent
(
intent
,
false
);
// If there's no valid Uri, return an error
if
(
paths
==
null
)
{
finishWithError
(
"missing_valid_image_uri"
,
"Cannot find at least one of the selected images."
);
return
;
}
handleMediaResult
(
paths
);
return
;
}
...
...
@@ -701,8 +751,14 @@ public class ImagePickerDelegate
private
void
handleChooseVideoResult
(
int
resultCode
,
Intent
data
)
{
if
(
resultCode
==
Activity
.
RESULT_OK
&&
data
!=
null
)
{
String
path
=
fileUtils
.
getPathFromUri
(
activity
,
data
.
getData
());
handleVideoResult
(
path
);
ArrayList
<
MediaPath
>
paths
=
getPathsFromIntent
(
data
,
false
);
// If there's no valid Uri, return an error
if
(
paths
==
null
||
paths
.
size
()
<
1
)
{
finishWithError
(
"no_valid_video_uri"
,
"Cannot find the selected video."
);
return
;
}
finishWithSuccess
(
paths
.
get
(
0
).
path
);
return
;
}
...
...
@@ -733,7 +789,7 @@ public class ImagePickerDelegate
localPendingCameraMediaUrl
!=
null
?
localPendingCameraMediaUrl
:
Uri
.
parse
(
cache
.
retrievePendingCameraMediaUriPath
()),
this
::
handleVideoResult
);
this
::
finishWithSuccess
);
return
;
}
...
...
@@ -796,10 +852,6 @@ public class ImagePickerDelegate
}
}
private
void
handleVideoResult
(
String
path
)
{
finishWithSuccess
(
path
);
}
private
boolean
setPendingOptionsAndResult
(
@Nullable
ImageSelectionOptions
imageOptions
,
@Nullable
VideoSelectionOptions
videoOptions
,
...
...
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
View file @
1651f6b
...
...
@@ -18,7 +18,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import
io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
;
import
io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter
;
import
io.flutter.plugin.common.BinaryMessenger
;
import
io.flutter.plugin.common.PluginRegistry
;
import
io.flutter.plugins.imagepicker.Messages.CacheRetrievalResult
;
import
io.flutter.plugins.imagepicker.Messages.FlutterError
;
import
io.flutter.plugins.imagepicker.Messages.GeneralOptions
;
...
...
@@ -117,7 +116,6 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
final
Activity
activity
,
final
BinaryMessenger
messenger
,
final
ImagePickerApi
handler
,
final
PluginRegistry
.
Registrar
registrar
,
final
ActivityPluginBinding
activityBinding
)
{
this
.
application
=
application
;
this
.
activity
=
activity
;
...
...
@@ -125,21 +123,15 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
this
.
messenger
=
messenger
;
delegate
=
constructDelegate
(
activity
);
ImagePickerApi
.
set
u
p
(
messenger
,
handler
);
ImagePickerApi
.
set
U
p
(
messenger
,
handler
);
observer
=
new
LifeCycleObserver
(
activity
);
if
(
registrar
!=
null
)
{
// V1 embedding setup for activity listeners.
application
.
registerActivityLifecycleCallbacks
(
observer
);
registrar
.
addActivityResultListener
(
delegate
);
registrar
.
addRequestPermissionsResultListener
(
delegate
);
}
else
{
// V2 embedding setup for activity listeners.
activityBinding
.
addActivityResultListener
(
delegate
);
activityBinding
.
addRequestPermissionsResultListener
(
delegate
);
lifecycle
=
FlutterLifecycleAdapter
.
getActivityLifecycle
(
activityBinding
);
lifecycle
.
addObserver
(
observer
);
}
}
// Only invoked by {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing.
ActivityState
(
final
ImagePickerDelegate
delegate
,
final
Activity
activity
)
{
...
...
@@ -159,7 +151,7 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
lifecycle
=
null
;
}
ImagePickerApi
.
set
u
p
(
messenger
,
null
);
ImagePickerApi
.
set
U
p
(
messenger
,
null
);
if
(
application
!=
null
)
{
application
.
unregisterActivityLifecycleCallbacks
(
observer
);
...
...
@@ -183,20 +175,6 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
private
FlutterPluginBinding
pluginBinding
;
ActivityState
activityState
;
@SuppressWarnings
(
"deprecation"
)
public
static
void
registerWith
(
@NonNull
io
.
flutter
.
plugin
.
common
.
PluginRegistry
.
Registrar
registrar
)
{
if
(
registrar
.
activity
()
==
null
)
{
// If a background flutter view tries to register the plugin, there will be no activity from the registrar,
// we stop the registering process immediately because the ImagePicker requires an activity.
return
;
}
Activity
activity
=
registrar
.
activity
();
Application
application
=
(
Application
)
(
registrar
.
context
().
getApplicationContext
());
ImagePickerPlugin
plugin
=
new
ImagePickerPlugin
();
plugin
.
setup
(
registrar
.
messenger
(),
application
,
activity
,
registrar
,
null
);
}
/**
* Default constructor for the plugin.
*
...
...
@@ -231,7 +209,6 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
pluginBinding
.
getBinaryMessenger
(),
(
Application
)
pluginBinding
.
getApplicationContext
(),
binding
.
getActivity
(),
null
,
binding
);
}
...
...
@@ -254,10 +231,8 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
final
BinaryMessenger
messenger
,
final
Application
application
,
final
Activity
activity
,
final
PluginRegistry
.
Registrar
registrar
,
final
ActivityPluginBinding
activityBinding
)
{
activityState
=
new
ActivityState
(
application
,
activity
,
messenger
,
this
,
registrar
,
activityBinding
);
activityState
=
new
ActivityState
(
application
,
activity
,
messenger
,
this
,
activityBinding
);
}
private
void
tearDown
()
{
...
...
@@ -317,7 +292,10 @@ public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePic
setCameraDevice
(
delegate
,
source
);
if
(
generalOptions
.
getAllowMultiple
())
{
delegate
.
chooseMultiImageFromGallery
(
options
,
generalOptions
.
getUsePhotoPicker
(),
result
);
int
limit
=
ImagePickerUtils
.
getLimitFromOption
(
generalOptions
);
delegate
.
chooseMultiImageFromGallery
(
options
,
generalOptions
.
getUsePhotoPicker
(),
limit
,
result
);
}
else
{
switch
(
source
.
getType
())
{
case
GALLERY:
...
...
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java
View file @
1651f6b
...
...
@@ -5,10 +5,13 @@
package
io
.
flutter
.
plugins
.
imagepicker
;
import
android.Manifest
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.content.pm.PackageInfo
;
import
android.content.pm.PackageManager
;
import
android.os.Build
;
import
android.provider.MediaStore
;
import
androidx.activity.result.contract.ActivityResultContracts
;
import
java.util.Arrays
;
final
class
ImagePickerUtils
{
...
...
@@ -16,10 +19,15 @@ final class ImagePickerUtils {
private
static
boolean
isPermissionPresentInManifest
(
Context
context
,
String
permissionName
)
{
try
{
PackageManager
packageManager
=
context
.
getPackageManager
();
// TODO(stuartmorgan): Add new codepath: https://github.com/flutter/flutter/issues/121816
@SuppressWarnings
(
"deprecation"
)
PackageInfo
packageInfo
=
packageManager
.
getPackageInfo
(
context
.
getPackageName
(),
PackageManager
.
GET_PERMISSIONS
);
PackageInfo
packageInfo
;
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
TIRAMISU
)
{
packageInfo
=
packageManager
.
getPackageInfo
(
context
.
getPackageName
(),
PackageManager
.
PackageInfoFlags
.
of
(
PackageManager
.
GET_PERMISSIONS
));
}
else
{
packageInfo
=
getPermissionsPackageInfoPreApi33
(
packageManager
,
context
.
getPackageName
());
}
String
[]
requestedPermissions
=
packageInfo
.
requestedPermissions
;
return
Arrays
.
asList
(
requestedPermissions
).
contains
(
permissionName
);
...
...
@@ -29,6 +37,13 @@ final class ImagePickerUtils {
}
}
@SuppressWarnings
(
"deprecation"
)
private
static
PackageInfo
getPermissionsPackageInfoPreApi33
(
PackageManager
packageManager
,
String
packageName
)
throws
PackageManager
.
NameNotFoundException
{
return
packageManager
.
getPackageInfo
(
packageName
,
PackageManager
.
GET_PERMISSIONS
);
}
/**
* Camera permission need request if it present in manifest, because for M or great for take Photo
* ar Video by intent need it permission, even if the camera permission is not used.
...
...
@@ -42,4 +57,32 @@ final class ImagePickerUtils {
boolean
greatOrEqualM
=
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
M
;
return
greatOrEqualM
&&
isPermissionPresentInManifest
(
context
,
Manifest
.
permission
.
CAMERA
);
}
/**
* The system photo picker has a maximum limit of selectable items returned by
* [MediaStore.getPickImagesMaxLimit()] On devices supporting picker provided via
* [ACTION_SYSTEM_FALLBACK_PICK_IMAGES], the limit may be ignored if it's higher than the allowed
* limit. On devices not supporting the photo picker, the limit is ignored.
*
* @see MediaStore.EXTRA_PICK_IMAGES_MAX
*/
@SuppressLint
({
"NewApi"
,
"ClassVerificationFailure"
})
static
int
getMaxItems
()
{
if
(
ActivityResultContracts
.
PickVisualMedia
.
isSystemPickerAvailable
$activity_release
())
{
return
MediaStore
.
getPickImagesMaxLimit
();
}
else
{
return
Integer
.
MAX_VALUE
;
}
}
static
int
getLimitFromOption
(
Messages
.
GeneralOptions
generalOptions
)
{
Long
limit
=
generalOptions
.
getLimit
();
int
effectiveLimit
=
getMaxItems
();
if
(
limit
!=
null
&&
limit
<
effectiveLimit
)
{
effectiveLimit
=
Math
.
toIntExact
(
limit
);
}
return
effectiveLimit
;
}
}
...
...
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java
View file @
1651f6b
...
...
@@ -10,7 +10,9 @@ import android.graphics.BitmapFactory;
import
android.util.Log
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
androidx.core.util.SizeFCompat
;
import
androidx.exifinterface.media.ExifInterface
;
import
java.io.ByteArrayOutputStream
;
import
java.io.File
;
import
java.io.FileOutputStream
;
...
...
@@ -81,47 +83,34 @@ class ImageResizer {
}
private
SizeFCompat
calculateTargetSize
(
@NonNull
Double
originalWidth
,
@NonNull
Double
originalHeight
,
double
originalWidth
,
double
originalHeight
,
@Nullable
Double
maxWidth
,
@Nullable
Double
maxHeight
)
{
double
aspectRatio
=
originalWidth
/
originalHeight
;
boolean
hasMaxWidth
=
maxWidth
!=
null
;
boolean
hasMaxHeight
=
maxHeight
!=
null
;
Double
width
=
hasMaxWidth
?
Math
.
min
(
originalWidth
,
maxWidth
)
:
originalWidth
;
Double
height
=
hasMaxHeight
?
Math
.
min
(
originalHeight
,
maxHeight
)
:
originalHeight
;
double
width
=
hasMaxWidth
?
Math
.
min
(
originalWidth
,
Math
.
round
(
maxWidth
))
:
originalWidth
;
double
height
=
hasMaxHeight
?
Math
.
min
(
originalHeight
,
Math
.
round
(
maxHeight
))
:
originalHeight
;
boolean
shouldDownscaleWidth
=
hasMaxWidth
&&
maxWidth
<
originalWidth
;
boolean
shouldDownscaleHeight
=
hasMaxHeight
&&
maxHeight
<
originalHeight
;
boolean
shouldDownscale
=
shouldDownscaleWidth
||
shouldDownscaleHeight
;
if
(
shouldDownscale
)
{
double
downscaledWidth
=
(
height
/
originalHeight
)
*
originalWidth
;
double
downscaledHeight
=
(
width
/
originalWidth
)
*
originalHeight
;
double
WidthForMaxHeight
=
height
*
aspectRatio
;
double
heightForMaxWidth
=
width
/
aspectRatio
;
if
(
width
<
height
)
{
if
(!
hasMaxWidth
)
{
width
=
downscaledWidth
;
if
(
heightForMaxWidth
>
height
)
{
width
=
(
double
)
Math
.
round
(
WidthForMaxHeight
);
}
else
{
height
=
downscaledHeight
;
}
}
else
if
(
height
<
width
)
{
if
(!
hasMaxHeight
)
{
height
=
downscaledHeight
;
}
else
{
width
=
downscaledWidth
;
}
}
else
{
if
(
originalWidth
<
originalHeight
)
{
width
=
downscaledWidth
;
}
else
if
(
originalHeight
<
originalWidth
)
{
height
=
downscaledHeight
;
}
height
=
(
double
)
Math
.
round
(
heightForMaxWidth
);
}
}
return
new
SizeFCompat
(
width
.
floatValue
(),
height
.
floatValue
()
);
return
new
SizeFCompat
(
(
float
)
width
,
(
float
)
height
);
}
private
File
createFile
(
File
externalFilesDirectory
,
String
child
)
{
...
...
@@ -137,10 +126,15 @@ class ImageResizer {
}
private
void
copyExif
(
String
filePathOri
,
String
filePathDest
)
{
exifDataCopier
.
copyExif
(
filePathOri
,
filePathDest
);
try
{
exifDataCopier
.
copyExif
(
new
ExifInterface
(
filePathOri
),
new
ExifInterface
(
filePathDest
));
}
catch
(
Exception
ex
)
{
Log
.
e
(
"ImageResizer"
,
"Error preserving Exif data on selected image: "
+
ex
);
}
}
private
SizeFCompat
readFileDimensions
(
String
path
)
{
@VisibleForTesting
SizeFCompat
readFileDimensions
(
String
path
)
{
BitmapFactory
.
Options
options
=
new
BitmapFactory
.
Options
();
options
.
inJustDecodeBounds
=
true
;
decodeFile
(
path
,
options
);
...
...
image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java
View file @
1651f6b
...
...
@@ -6,6 +6,9 @@
package
io
.
flutter
.
plugins
.
imagepicker
;
import
static
java
.
lang
.
annotation
.
ElementType
.
METHOD
;
import
static
java
.
lang
.
annotation
.
RetentionPolicy
.
CLASS
;
import
android.util.Log
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
...
...
@@ -14,6 +17,8 @@ import io.flutter.plugin.common.BinaryMessenger;
import
io.flutter.plugin.common.MessageCodec
;
import
io.flutter.plugin.common.StandardMessageCodec
;
import
java.io.ByteArrayOutputStream
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.Target
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.List
;
...
...
@@ -55,6 +60,10 @@ public class Messages {
return
errorList
;
}
@Target
(
METHOD
)
@Retention
(
CLASS
)
@interface
CanIgnoreReturnValue
{}
public
enum
SourceCamera
{
REAR
(
0
),
FRONT
(
1
);
...
...
@@ -116,6 +125,16 @@ public class Messages {
this
.
usePhotoPicker
=
setterArg
;
}
private
@Nullable
Long
limit
;
public
@Nullable
Long
getLimit
()
{
return
limit
;
}
public
void
setLimit
(
@Nullable
Long
setterArg
)
{
this
.
limit
=
setterArg
;
}
/** Constructor is non-public to enforce null safety; use Builder. */
GeneralOptions
()
{}
...
...
@@ -123,6 +142,7 @@ public class Messages {
private
@Nullable
Boolean
allowMultiple
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setAllowMultiple
(
@NonNull
Boolean
setterArg
)
{
this
.
allowMultiple
=
setterArg
;
return
this
;
...
...
@@ -130,24 +150,35 @@ public class Messages {
private
@Nullable
Boolean
usePhotoPicker
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setUsePhotoPicker
(
@NonNull
Boolean
setterArg
)
{
this
.
usePhotoPicker
=
setterArg
;
return
this
;
}
private
@Nullable
Long
limit
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setLimit
(
@Nullable
Long
setterArg
)
{
this
.
limit
=
setterArg
;
return
this
;
}
public
@NonNull
GeneralOptions
build
()
{
GeneralOptions
pigeonReturn
=
new
GeneralOptions
();
pigeonReturn
.
setAllowMultiple
(
allowMultiple
);
pigeonReturn
.
setUsePhotoPicker
(
usePhotoPicker
);
pigeonReturn
.
setLimit
(
limit
);
return
pigeonReturn
;
}
}
@NonNull
ArrayList
<
Object
>
toList
()
{
ArrayList
<
Object
>
toListResult
=
new
ArrayList
<
Object
>(
2
);
ArrayList
<
Object
>
toListResult
=
new
ArrayList
<
Object
>(
3
);
toListResult
.
add
(
allowMultiple
);
toListResult
.
add
(
usePhotoPicker
);
toListResult
.
add
(
limit
);
return
toListResult
;
}
...
...
@@ -157,6 +188,9 @@ public class Messages {
pigeonResult
.
setAllowMultiple
((
Boolean
)
allowMultiple
);
Object
usePhotoPicker
=
list
.
get
(
1
);
pigeonResult
.
setUsePhotoPicker
((
Boolean
)
usePhotoPicker
);
Object
limit
=
list
.
get
(
2
);
pigeonResult
.
setLimit
(
(
limit
==
null
)
?
null
:
((
limit
instanceof
Integer
)
?
(
Integer
)
limit
:
(
Long
)
limit
));
return
pigeonResult
;
}
}
...
...
@@ -214,6 +248,7 @@ public class Messages {
private
@Nullable
Double
maxWidth
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setMaxWidth
(
@Nullable
Double
setterArg
)
{
this
.
maxWidth
=
setterArg
;
return
this
;
...
...
@@ -221,6 +256,7 @@ public class Messages {
private
@Nullable
Double
maxHeight
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setMaxHeight
(
@Nullable
Double
setterArg
)
{
this
.
maxHeight
=
setterArg
;
return
this
;
...
...
@@ -228,6 +264,7 @@ public class Messages {
private
@Nullable
Long
quality
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setQuality
(
@NonNull
Long
setterArg
)
{
this
.
quality
=
setterArg
;
return
this
;
...
...
@@ -288,6 +325,7 @@ public class Messages {
private
@Nullable
ImageSelectionOptions
imageSelectionOptions
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setImageSelectionOptions
(
@NonNull
ImageSelectionOptions
setterArg
)
{
this
.
imageSelectionOptions
=
setterArg
;
return
this
;
...
...
@@ -339,6 +377,7 @@ public class Messages {
private
@Nullable
Long
maxDurationSeconds
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setMaxDurationSeconds
(
@Nullable
Long
setterArg
)
{
this
.
maxDurationSeconds
=
setterArg
;
return
this
;
...
...
@@ -407,6 +446,7 @@ public class Messages {
private
@Nullable
SourceType
type
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setType
(
@NonNull
SourceType
setterArg
)
{
this
.
type
=
setterArg
;
return
this
;
...
...
@@ -414,6 +454,7 @@ public class Messages {
private
@Nullable
SourceCamera
camera
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setCamera
(
@Nullable
SourceCamera
setterArg
)
{
this
.
camera
=
setterArg
;
return
this
;
...
...
@@ -438,7 +479,7 @@ public class Messages {
static
@NonNull
SourceSpecification
fromList
(
@NonNull
ArrayList
<
Object
>
list
)
{
SourceSpecification
pigeonResult
=
new
SourceSpecification
();
Object
type
=
list
.
get
(
0
);
pigeonResult
.
setType
(
type
==
null
?
null
:
SourceType
.
values
()[(
int
)
type
]);
pigeonResult
.
setType
(
SourceType
.
values
()[(
int
)
type
]);
Object
camera
=
list
.
get
(
1
);
pigeonResult
.
setCamera
(
camera
==
null
?
null
:
SourceCamera
.
values
()[(
int
)
camera
]);
return
pigeonResult
;
...
...
@@ -483,6 +524,7 @@ public class Messages {
private
@Nullable
String
code
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setCode
(
@NonNull
String
setterArg
)
{
this
.
code
=
setterArg
;
return
this
;
...
...
@@ -490,6 +532,7 @@ public class Messages {
private
@Nullable
String
message
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setMessage
(
@Nullable
String
setterArg
)
{
this
.
message
=
setterArg
;
return
this
;
...
...
@@ -578,6 +621,7 @@ public class Messages {
private
@Nullable
CacheRetrievalType
type
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setType
(
@NonNull
CacheRetrievalType
setterArg
)
{
this
.
type
=
setterArg
;
return
this
;
...
...
@@ -585,6 +629,7 @@ public class Messages {
private
@Nullable
CacheRetrievalError
error
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setError
(
@Nullable
CacheRetrievalError
setterArg
)
{
this
.
error
=
setterArg
;
return
this
;
...
...
@@ -592,6 +637,7 @@ public class Messages {
private
@Nullable
List
<
String
>
paths
;
@CanIgnoreReturnValue
public
@NonNull
Builder
setPaths
(
@NonNull
List
<
String
>
setterArg
)
{
this
.
paths
=
setterArg
;
return
this
;
...
...
@@ -618,7 +664,7 @@ public class Messages {
static
@NonNull
CacheRetrievalResult
fromList
(
@NonNull
ArrayList
<
Object
>
list
)
{
CacheRetrievalResult
pigeonResult
=
new
CacheRetrievalResult
();
Object
type
=
list
.
get
(
0
);
pigeonResult
.
setType
(
type
==
null
?
null
:
CacheRetrievalType
.
values
()[(
int
)
type
]);
pigeonResult
.
setType
(
CacheRetrievalType
.
values
()[(
int
)
type
]);
Object
error
=
list
.
get
(
1
);
pigeonResult
.
setError
(
(
error
==
null
)
?
null
:
CacheRetrievalError
.
fromList
((
ArrayList
<
Object
>)
error
));
...
...
@@ -734,7 +780,7 @@ public class Messages {
return
ImagePickerApiCodec
.
INSTANCE
;
}
/** Sets up an instance of `ImagePickerApi` to handle messages through the `binaryMessenger`. */
static
void
set
u
p
(
@NonNull
BinaryMessenger
binaryMessenger
,
@Nullable
ImagePickerApi
api
)
{
static
void
set
U
p
(
@NonNull
BinaryMessenger
binaryMessenger
,
@Nullable
ImagePickerApi
api
)
{
{
BinaryMessenger
.
TaskQueue
taskQueue
=
binaryMessenger
.
makeBackgroundTaskQueue
();
BasicMessageChannel
<
Object
>
channel
=
...
...
image_picker_android/lib/image_picker_android.dart
View file @
1651f6b
...
...
@@ -67,6 +67,7 @@ class ImagePickerAndroid extends ImagePickerPlatform {
double
?
maxWidth
,
double
?
maxHeight
,
int
?
imageQuality
,
int
?
limit
,
})
{
if
(
imageQuality
!=
null
&&
(
imageQuality
<
0
||
imageQuality
>
100
))
{
throw
ArgumentError
.
value
(
...
...
@@ -81,6 +82,10 @@ class ImagePickerAndroid extends ImagePickerPlatform {
throw
ArgumentError
.
value
(
maxHeight
,
'maxHeight'
,
'cannot be negative'
);
}
if
(
limit
!=
null
&&
limit
<
2
)
{
throw
ArgumentError
.
value
(
limit
,
'limit'
,
'cannot be lower than 2'
);
}
return
_hostApi
.
pickImages
(
SourceSpecification
(
type:
SourceType
.
gallery
),
ImageSelectionOptions
(
...
...
@@ -88,7 +93,10 @@ class ImagePickerAndroid extends ImagePickerPlatform {
maxHeight:
maxHeight
,
quality:
imageQuality
??
100
),
GeneralOptions
(
allowMultiple:
true
,
usePhotoPicker:
useAndroidPhotoPicker
),
allowMultiple:
true
,
usePhotoPicker:
useAndroidPhotoPicker
,
limit:
limit
,
),
);
}
...
...
@@ -210,15 +218,30 @@ class ImagePickerAndroid extends ImagePickerPlatform {
}
@override
Future
<
List
<
XFile
>>
getMultiImageWithOptions
({
MultiImagePickerOptions
options
=
const
MultiImagePickerOptions
(),
})
async
{
final
List
<
dynamic
>
paths
=
await
_getMultiImagePath
(
maxWidth:
options
.
imageOptions
.
maxWidth
,
maxHeight:
options
.
imageOptions
.
maxHeight
,
imageQuality:
options
.
imageOptions
.
imageQuality
,
limit:
options
.
limit
,
);
if
(
paths
.
isEmpty
)
{
return
<
XFile
>[];
}
return
paths
.
map
((
dynamic
path
)
=>
XFile
(
path
as
String
)).
toList
();
}
@override
Future
<
List
<
XFile
>>
getMedia
({
required
MediaOptions
options
,
})
async
{
return
(
await
_hostApi
.
pickMedia
(
_mediaOptionsToMediaSelectionOptions
(
options
),
GeneralOptions
(
allowMultiple:
options
.
allowMultiple
,
usePhotoPicker:
useAndroidPhotoPicker
,
),
_mediaOptionsToGeneralOptions
(
options
),
))
.
map
((
String
?
path
)
=>
XFile
(
path
!))
.
toList
();
...
...
@@ -243,6 +266,7 @@ class ImagePickerAndroid extends ImagePickerPlatform {
final
ImageSelectionOptions
imageSelectionOptions
=
_imageOptionsToImageSelectionOptionsWithValidator
(
mediaOptions
.
imageOptions
);
return
MediaSelectionOptions
(
imageSelectionOptions:
imageSelectionOptions
,
);
...
...
@@ -270,6 +294,29 @@ class ImagePickerAndroid extends ImagePickerPlatform {
quality:
imageQuality
??
100
,
maxHeight:
maxHeight
,
maxWidth:
maxWidth
);
}
GeneralOptions
_mediaOptionsToGeneralOptions
(
MediaOptions
options
)
{
final
bool
allowMultiple
=
options
.
allowMultiple
;
final
int
?
limit
=
options
.
limit
;
if
(!
allowMultiple
&&
limit
!=
null
)
{
throw
ArgumentError
.
value
(
allowMultiple
,
'allowMultiple'
,
'cannot be false, when limit is not null'
,
);
}
if
(
limit
!=
null
&&
limit
<
2
)
{
throw
ArgumentError
.
value
(
limit
,
'limit'
,
'cannot be lower then 2'
);
}
return
GeneralOptions
(
allowMultiple:
allowMultiple
,
usePhotoPicker:
useAndroidPhotoPicker
,
limit:
limit
,
);
}
@override
Future
<
LostData
>
retrieveLostData
()
async
{
final
LostDataResponse
result
=
await
getLostData
();
...
...
image_picker_android/lib/src/messages.g.dart
View file @
1651f6b
...
...
@@ -30,16 +30,20 @@ class GeneralOptions {
GeneralOptions
({
required
this
.
allowMultiple
,
required
this
.
usePhotoPicker
,
this
.
limit
,
});
bool
allowMultiple
;
bool
usePhotoPicker
;
int
?
limit
;
Object
encode
()
{
return
<
Object
?>[
allowMultiple
,
usePhotoPicker
,
limit
,
];
}
...
...
@@ -48,6 +52,7 @@ class GeneralOptions {
return
GeneralOptions
(
allowMultiple:
result
[
0
]!
as
bool
,
usePhotoPicker:
result
[
1
]!
as
bool
,
limit:
result
[
2
]
as
int
?,
);
}
}
...
...
@@ -195,7 +200,7 @@ class CacheRetrievalResult {
CacheRetrievalResult
({
required
this
.
type
,
this
.
error
,
required
this
.
paths
,
this
.
paths
=
const
<
String
>[]
,
});
/// The type of the retrieved data.
...
...
image_picker_ohos/lib/image_picker_ohos.dart
View file @
1651f6b
...
...
@@ -56,6 +56,7 @@ class ImagePickerOhos extends ImagePickerPlatform {
double
?
maxWidth
,
double
?
maxHeight
,
int
?
imageQuality
,
int
?
limit
,
})
{
if
(
imageQuality
!=
null
&&
(
imageQuality
<
0
||
imageQuality
>
100
))
{
throw
ArgumentError
.
value
(
...
...
@@ -70,6 +71,10 @@ class ImagePickerOhos extends ImagePickerPlatform {
throw
ArgumentError
.
value
(
maxHeight
,
'maxHeight'
,
'cannot be negative'
);
}
if
(
limit
!=
null
&&
limit
<
2
)
{
throw
ArgumentError
.
value
(
limit
,
'limit'
,
'cannot be lower than 2'
);
}
return
_hostApi
.
pickImages
(
SourceSpecification
(
type:
SourceType
.
gallery
),
ImageSelectionOptions
(
...
...
@@ -77,7 +82,10 @@ class ImagePickerOhos extends ImagePickerPlatform {
maxHeight:
maxHeight
,
quality:
imageQuality
??
100
),
GeneralOptions
(
allowMultiple:
true
,
usePhotoPicker:
useOhosPhotoPicker
),
allowMultiple:
true
,
usePhotoPicker:
useOhosPhotoPicker
,
limit:
limit
,
),
);
}
...
...
@@ -199,15 +207,30 @@ class ImagePickerOhos extends ImagePickerPlatform {
}
@override
Future
<
List
<
XFile
>>
getMultiImageWithOptions
({
MultiImagePickerOptions
options
=
const
MultiImagePickerOptions
(),
})
async
{
final
List
<
dynamic
>
paths
=
await
_getMultiImagePath
(
maxWidth:
options
.
imageOptions
.
maxWidth
,
maxHeight:
options
.
imageOptions
.
maxHeight
,
imageQuality:
options
.
imageOptions
.
imageQuality
,
limit:
options
.
limit
,
);
if
(
paths
.
isEmpty
)
{
return
<
XFile
>[];
}
return
paths
.
map
((
dynamic
path
)
=>
XFile
(
path
as
String
)).
toList
();
}
@override
Future
<
List
<
XFile
>>
getMedia
({
required
MediaOptions
options
,
})
async
{
return
(
await
_hostApi
.
pickMedia
(
_mediaOptionsToMediaSelectionOptions
(
options
),
GeneralOptions
(
allowMultiple:
options
.
allowMultiple
,
usePhotoPicker:
useOhosPhotoPicker
,
),
_mediaOptionsToGeneralOptions
(
options
),
))
.
map
((
String
?
path
)
=>
XFile
(
path
!))
.
toList
();
...
...
@@ -232,6 +255,7 @@ class ImagePickerOhos extends ImagePickerPlatform {
final
ImageSelectionOptions
imageSelectionOptions
=
_imageOptionsToImageSelectionOptionsWithValidator
(
mediaOptions
.
imageOptions
);
return
MediaSelectionOptions
(
imageSelectionOptions:
imageSelectionOptions
,
);
...
...
@@ -259,6 +283,29 @@ class ImagePickerOhos extends ImagePickerPlatform {
quality:
imageQuality
??
100
,
maxHeight:
maxHeight
,
maxWidth:
maxWidth
);
}
GeneralOptions
_mediaOptionsToGeneralOptions
(
MediaOptions
options
)
{
final
bool
allowMultiple
=
options
.
allowMultiple
;
final
int
?
limit
=
options
.
limit
;
if
(!
allowMultiple
&&
limit
!=
null
)
{
throw
ArgumentError
.
value
(
allowMultiple
,
'allowMultiple'
,
'cannot be false, when limit is not null'
,
);
}
if
(
limit
!=
null
&&
limit
<
2
)
{
throw
ArgumentError
.
value
(
limit
,
'limit'
,
'cannot be lower then 2'
);
}
return
GeneralOptions
(
allowMultiple:
allowMultiple
,
usePhotoPicker:
useOhosPhotoPicker
,
limit:
limit
,
);
}
@override
Future
<
LostData
>
retrieveLostData
()
async
{
final
LostDataResponse
result
=
await
getLostData
();
...
...
image_picker_ohos/lib/src/messages.g.dart
View file @
1651f6b
...
...
@@ -30,16 +30,20 @@ class GeneralOptions {
GeneralOptions
({
required
this
.
allowMultiple
,
required
this
.
usePhotoPicker
,
this
.
limit
,
});
bool
allowMultiple
;
bool
usePhotoPicker
;
int
?
limit
;
Object
encode
()
{
return
<
Object
?>[
allowMultiple
,
usePhotoPicker
,
limit
,
];
}
...
...
@@ -48,6 +52,7 @@ class GeneralOptions {
return
GeneralOptions
(
allowMultiple:
result
[
0
]!
as
bool
,
usePhotoPicker:
result
[
1
]!
as
bool
,
limit:
result
[
2
]
as
int
?,
);
}
}
...
...
@@ -195,7 +200,7 @@ class CacheRetrievalResult {
CacheRetrievalResult
({
required
this
.
type
,
this
.
error
,
required
this
.
paths
,
this
.
paths
=
const
<
String
>[]
,
});
/// The type of the retrieved data.
...
...
image_picker_ohos/ohos/src/main/ets/image_picker/ImagePickerDelegate.ets
View file @
1651f6b
...
...
@@ -163,7 +163,7 @@ export default class ImagePickerDelegate {
return;
}
this.chooseMedia(generalOptions.getAllowMultiple() ?
9
: 1, 'handleChooseMediaResult')
this.chooseMedia(generalOptions.getAllowMultiple() ?
generalOptions.getLimit()
: 1, 'handleChooseMediaResult')
}
handleChooseMediaResult(code: number, uris: Array<string>): void {
...
...
@@ -279,13 +279,13 @@ export default class ImagePickerDelegate {
}
// 选择多个图片
chooseMultiImagesFromGallery(options: ImageSelectionOptions, usePhotoPicker: boolean, result: Result<ArrayList<string>>): void {
chooseMultiImagesFromGallery(options: ImageSelectionOptions,
limit: number,
usePhotoPicker: boolean, result: Result<ArrayList<string>>): void {
if (!this.setPendingOptionsAndResult(options, null, result)) {
this.finishWithAlreadyActiveError(result);
return;
}
this.chooseMedia(
9
, 'handleChooseMediaResult', photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE)
this.chooseMedia(
limit
, 'handleChooseMediaResult', photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE)
}
// 唤起相机拍照
...
...
image_picker_ohos/ohos/src/main/ets/image_picker/ImagePickerPlugin.ets
View file @
1651f6b
...
...
@@ -95,7 +95,7 @@ export default class ImagePickerPlugin implements FlutterPlugin, AbilityAware {
this.setCameraDevice(delegate, source);
if (generalOptions.getAllowMultiple()) {
delegate.chooseMultiImagesFromGallery(options, generalOptions.getUsePhotoPicker(), result);
delegate.chooseMultiImagesFromGallery(options,
generalOptions.getLimit(),
generalOptions.getUsePhotoPicker(), result);
} else {
switch (source.getType()) {
case SourceType.GALLERY: {
...
...
image_picker_ohos/ohos/src/main/ets/image_picker/Messages.ets
View file @
1651f6b
...
...
@@ -60,11 +60,14 @@ export class FlutterError extends Error {
class GeneralOptionsBuilder {
setAllowMultiple: (setterArg: boolean) => ESObject
setUsePhotoPicker: (setterArg: boolean) => ESObject
setLimit: (setterArg: number) => ESObject
build: () => ESObject
constructor(setAllowMultiple: (setterArg: boolean) => ESObject, setUsePhotoPicker: (setterArg: boolean) => ESObject, build: () => ESObject) {
constructor(setAllowMultiple: (setterArg: boolean) => ESObject, setUsePhotoPicker: (setterArg: boolean) => ESObject,
setLimit: (setterArg: number) => ESObject,
build: () => ESObject) {
this.setAllowMultiple = setAllowMultiple
this.setUsePhotoPicker = setUsePhotoPicker
this.setLimit = setLimit
this.build = build
}
}
...
...
@@ -72,6 +75,7 @@ class GeneralOptionsBuilder {
export class GeneralOptions {
private allowMultiple: boolean = false;
private usePhotoPicker: boolean = false;
private limit: number = 1;
private constructor() {
}
...
...
@@ -98,16 +102,33 @@ export class GeneralOptions {
this.usePhotoPicker = setterArg;
}
getLimit(): number {
return this.limit;
}
setLimit(setterArg: number): void {
if (setterArg == null) {
throw new Error("Nonnull field \"limit\" is null.");
}
this.limit = setterArg;
}
public Builder: ESObject = new GeneralOptionsBuilder((setterArg: boolean) => {
this.allowMultiple = setterArg;
return this;
}, (setterArg: boolean) => {
this.usePhotoPicker = setterArg;
return this;
}, (setterArg: number) => {
this.limit = setterArg;
return this;
}, (): ESObject => {
const pigeonReturn: ESObject = new GeneralOptions();
pigeonReturn.setAllowMultiple(this.allowMultiple);
pigeonReturn.setUsePhotoPicker(this.usePhotoPicker);
pigeonReturn.setUsePhotoPicker(this.limit);
return pigeonReturn;
}
)
...
...
@@ -116,6 +137,7 @@ export class GeneralOptions {
const toListResult: ArrayList<ESObject> = new ArrayList<ESObject>();
toListResult.add(this.allowMultiple);
toListResult.add(this.usePhotoPicker);
toListResult.add(this.limit);
return toListResult;
}
...
...
@@ -125,6 +147,8 @@ export class GeneralOptions {
pigeonResult.setAllowMultiple(allowMultiple);
const usePhotoPicker: ESObject = list[1];
pigeonResult.setUsePhotoPicker(usePhotoPicker);
const limit: ESObject = list[2];
pigeonResult.setLimit(limit);
return pigeonResult;
}
}
...
...
image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
View file @
1651f6b
...
...
@@ -58,6 +58,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
double
?
maxHeight
,
int
?
imageQuality
,
bool
requestFullMetadata
=
true
,
int
?
limit
,
})
{
if
(
imageQuality
!=
null
&&
(
imageQuality
<
0
||
imageQuality
>
100
))
{
throw
ArgumentError
.
value
(
...
...
@@ -79,6 +80,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
'maxHeight'
:
maxHeight
,
'imageQuality'
:
imageQuality
,
'requestFullMetadata'
:
requestFullMetadata
,
'limit'
:
limit
,
},
);
}
...
...
@@ -244,6 +246,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
maxHeight:
options
.
imageOptions
.
maxHeight
,
imageQuality:
options
.
imageOptions
.
imageQuality
,
requestFullMetadata:
options
.
imageOptions
.
requestFullMetadata
,
limit:
options
.
limit
,
);
if
(
paths
==
null
)
{
return
<
XFile
>[];
...
...
@@ -263,6 +266,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
'maxImageHeight'
:
imageOptions
.
maxHeight
,
'imageQuality'
:
imageOptions
.
imageQuality
,
'allowMultiple'
:
options
.
allowMultiple
,
'limit'
:
options
.
limit
,
};
final
List
<
XFile
>?
paths
=
await
_channel
...
...
@@ -312,13 +316,10 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
switch
(
type
)
{
case
kTypeImage:
retrieveType
=
RetrieveType
.
image
;
break
;
case
kTypeVideo:
retrieveType
=
RetrieveType
.
video
;
break
;
case
kTypeMedia:
retrieveType
=
RetrieveType
.
media
;
break
;
}
PlatformException
?
exception
;
...
...
image_picker_platform_interface/lib/src/types/media_options.dart
View file @
1651f6b
...
...
@@ -13,11 +13,48 @@ class MediaOptions {
const
MediaOptions
({
this
.
imageOptions
=
const
ImageOptions
(),
required
this
.
allowMultiple
,
this
.
limit
,
});
/// Construct a new MediaOptions instance.
///
/// Throws if limit is lower than 2,
/// or allowMultiple is false and limit is not null.
MediaOptions
.
createAndValidate
({
this
.
imageOptions
=
const
ImageOptions
(),
required
this
.
allowMultiple
,
this
.
limit
,
})
{
_validate
(
allowMultiple:
allowMultiple
,
limit:
limit
);
}
/// Options that will apply to images upon selection.
final
ImageOptions
imageOptions
;
/// Whether to allow for selecting multiple media.
final
bool
allowMultiple
;
/// The maximum number of images to select.
///
/// Default null value means no limit.
/// This value may be ignored by platforms that cannot support it.
final
int
?
limit
;
/// Validates that all values are within required ranges.
///
/// Throws if limit is lower than 2,
/// or allowMultiple is false and limit is not null.
static
void
_validate
({
required
bool
allowMultiple
,
int
?
limit
})
{
if
(!
allowMultiple
&&
limit
!=
null
)
{
throw
ArgumentError
.
value
(
allowMultiple
,
'allowMultiple'
,
'cannot be false, when limit is not null'
,
);
}
if
(
limit
!=
null
&&
limit
<
2
)
{
throw
ArgumentError
.
value
(
limit
,
'limit'
,
'cannot be lower then 2'
);
}
}
}
...
...
image_picker_platform_interface/lib/src/types/multi_image_picker_options.dart
View file @
1651f6b
...
...
@@ -6,11 +6,37 @@ import 'image_options.dart';
/// Specifies options for picking multiple images from the device's gallery.
class
MultiImagePickerOptions
{
/// Creates an instance with the given [imageOptions].
/// Creates an instance with the given [imageOptions]
and [limit]
.
const
MultiImagePickerOptions
({
this
.
imageOptions
=
const
ImageOptions
(),
this
.
limit
,
});
/// Creates an instance with the given [imageOptions] and [limit].
///
/// Throws if limit is lower than 2.
MultiImagePickerOptions
.
createAndValidate
({
this
.
imageOptions
=
const
ImageOptions
(),
this
.
limit
,
})
{
_validate
(
limit:
limit
);
}
/// The image-specific options for picking.
final
ImageOptions
imageOptions
;
/// The maximum number of images to select.
///
/// Default null value means no limit.
/// This value may be ignored by platforms that cannot support it.
final
int
?
limit
;
/// Validates that all values are within required ranges.
///
/// Throws if limit is lower than 2.
static
void
_validate
({
int
?
limit
})
{
if
(
limit
!=
null
&&
limit
<
2
)
{
throw
ArgumentError
.
value
(
limit
,
'limit'
,
'cannot be lower then 2'
);
}
}
}
...
...
Please
register
or
login
to post a comment