Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Richard Bushnell 2021-03-18 11:33:07 +00:00
commit 68e63e3b93
13 changed files with 366 additions and 783 deletions

76
.gitignore vendored
View File

@ -1 +1,75 @@
.DS_Store .dart_tool/ .packages .pub/ build/ .idea/ *.iml
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
pubspec.lock
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

@ -1,8 +1,22 @@
## 0.4.0
Add ```cleanCache``` api to clean the cache directory.
Add ```mimeType``` attribute of ```Medium```.
Add alternative media query syntax to support Android 11.
Cache original image data to a cached file and keep original medium file extension in iOS.
Fix a problem of collection possibly be nil.
Update .gitignore file.
## 0.3.0
Force getFile to use high quality format for videos in iOS platform.
Force ```getFile``` to use high quality format for videos in iOS platform.
Add optional mediumType parameter of getAlbumThumbnail api method to display video thumbnail correctly.
Add optional ```mediumType``` parameter of ```getAlbumThumbnail``` api method to display video thumbnail correctly.
## 0.2.5
@ -12,7 +26,7 @@ Remove ```MediumType``` parameter in ```_listMedia``` method.
## 0.2.4
Add VideoProvider widget to play video in plugin example.
Add ```VideoProvider``` widget to play video in plugin example.
## 0.2.3
@ -20,9 +34,9 @@ Add ```MediumType``` attribute in ```ThumbnailProvider```.
Fix a bug that throw ```FileNotFountException``` when load image and video thumbnail doesn't exists on Android API 29+.
Change medium creationDate and modifiedDate precision from second to millisecond on iOS platform.
Change medium ```creationDate``` and ```modifiedDate``` precision from second to millisecond on iOS platform.
Add video duration attribute in Medium.
Add video duration attribute in ```Medium``.
## 0.2.0+1

Binary file not shown.

160
android/gradlew vendored
View File

@ -1,160 +0,0 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
android/gradlew.bat vendored
View File

@ -1,90 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -10,6 +10,7 @@ import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import android.graphics.Bitmap
import java.io.ByteArrayOutputStream
import java.io.File
import android.provider.MediaStore
import android.content.Context
import android.database.Cursor
@ -55,6 +56,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_MODIFIED
)
@ -63,6 +65,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
MediaStore.Video.Media._ID,
MediaStore.Video.Media.WIDTH,
MediaStore.Video.Media.HEIGHT,
MediaStore.Video.Media.MIME_TYPE,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.DATE_TAKEN,
MediaStore.Video.Media.DATE_MODIFIED
@ -140,6 +143,10 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
result.success(v)
})
}
"cleanCache" -> {
cleanCache()
result.success(null)
}
else -> result.notImplemented()
}
}
@ -277,14 +284,9 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
val limit = take ?: (total - offset)
this.context?.run {
val imageCursor: Cursor?
var imageCursor: Cursor? = null
/**
* Change the way to fetch Media Store
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Get All data in Cursor by sorting in DESC order
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
imageCursor = this.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
imageMetadataProjection,
@ -292,7 +294,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
// Limit & Offset
putInt(android.content.ContentResolver.QUERY_ARG_LIMIT, limit)
putInt(android.content.ContentResolver.QUERY_ARG_OFFSET, offset)
// Sort function
// Sort
putStringArray(
android.content.ContentResolver.QUERY_ARG_SORT_COLUMNS,
arrayOf(
@ -310,12 +312,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
// Selection
if (albumId != allAlbumId) {
putString(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Images.Media.BUCKET_ID} = ?")
putStringArray(
android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
arrayOf(
albumId.toString()
)
)
putStringArray(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(albumId))
}
},
null
@ -350,12 +347,48 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
val limit = take ?: (total - offset)
this.context?.run {
val videoCursor = this.contentResolver.query(
val videoCursor: Cursor?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
videoCursor = this.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
videoMetadataProjection,
if (albumId == allAlbumId) null else "${MediaStore.Images.Media.BUCKET_ID} = $albumId",
android.os.Bundle().apply {
// Limit & Offset
putInt(android.content.ContentResolver.QUERY_ARG_LIMIT, limit)
putInt(android.content.ContentResolver.QUERY_ARG_OFFSET, offset)
// Sort
putStringArray(
android.content.ContentResolver.QUERY_ARG_SORT_COLUMNS,
arrayOf(
MediaStore.Video.Media.DATE_TAKEN,
MediaStore.Video.Media.DATE_MODIFIED
)
)
putIntArray(
android.content.ContentResolver.QUERY_ARG_SORT_DIRECTION,
intArrayOf(
android.content.ContentResolver.QUERY_SORT_DIRECTION_DESCENDING,
android.content.ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
)
)
// Selection
if (albumId != allAlbumId) {
putString(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Video.Media.BUCKET_ID} = ?")
putStringArray(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(albumId))
}
},
null
)
} else {
videoCursor = this.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
videoMetadataProjection,
if (albumId == allAlbumId) null else "${MediaStore.Video.Media.BUCKET_ID} = $albumId",
null,
"$videoOrderBy LIMIT $limit OFFSET $offset")
"$videoOrderBy LIMIT $limit OFFSET $offset"
)
}
videoCursor?.use { cursor ->
while (cursor.moveToNext()) {
@ -510,27 +543,62 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
private fun getAlbumThumbnail(albumId: String, mediumType: String?, width: Int?, height: Int?): ByteArray? {
return when (mediumType) {
imageType -> {
getImageAlbumThubnail(albumId, width, height)
getImageAlbumThumbnail(albumId, width, height)
}
videoType -> {
getVideoAlbumThubnail(albumId, width, height)
getVideoAlbumThumbnail(albumId, width, height)
}
else -> {
getImageAlbumThubnail(albumId, width, height)
?: getVideoAlbumThubnail(albumId, width, height)
getImageAlbumThumbnail(albumId, width, height)
?: getVideoAlbumThumbnail(albumId, width, height)
}
}
}
private fun getImageAlbumThubnail(albumId: String, width: Int?, height: Int?): ByteArray? {
private fun getImageAlbumThumbnail(albumId: String, width: Int?, height: Int?): ByteArray? {
return this.context?.run {
val imageCursor = this.contentResolver.query(
val imageCursor: Cursor?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
imageCursor = this.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID),
android.os.Bundle().apply {
// Limit
putInt(android.content.ContentResolver.QUERY_ARG_LIMIT, 1)
// Sort
putStringArray(
android.content.ContentResolver.QUERY_ARG_SORT_COLUMNS,
arrayOf(
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_MODIFIED
)
)
putIntArray(
android.content.ContentResolver.QUERY_ARG_SORT_DIRECTION,
intArrayOf(
android.content.ContentResolver.QUERY_SORT_DIRECTION_DESCENDING,
android.content.ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
)
)
// Selection
if (albumId != allAlbumId) {
putString(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Images.Media.BUCKET_ID} = ?")
putStringArray(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(albumId))
}
},
null
)
} else {
imageCursor = this.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID),
if (albumId == allAlbumId) null else "${MediaStore.Images.Media.BUCKET_ID} = $albumId",
null,
MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT 1"
)
}
imageCursor?.use { cursor ->
if (cursor.moveToFirst()) {
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
@ -543,15 +611,50 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
}
}
private fun getVideoAlbumThubnail(albumId: String, width: Int?, height: Int?): ByteArray? {
private fun getVideoAlbumThumbnail(albumId: String, width: Int?, height: Int?): ByteArray? {
return this.context?.run {
val videoCursor = this.contentResolver.query(
val videoCursor: Cursor?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
videoCursor = this.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Video.Media._ID),
android.os.Bundle().apply {
// Limit
putInt(android.content.ContentResolver.QUERY_ARG_LIMIT, 1)
// Sort
putStringArray(
android.content.ContentResolver.QUERY_ARG_SORT_COLUMNS,
arrayOf(
MediaStore.Video.Media.DATE_TAKEN,
MediaStore.Video.Media.DATE_MODIFIED
)
)
putIntArray(
android.content.ContentResolver.QUERY_ARG_SORT_DIRECTION,
intArrayOf(
android.content.ContentResolver.QUERY_SORT_DIRECTION_DESCENDING,
android.content.ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
)
)
// Selection
if (albumId != allAlbumId) {
putString(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Video.Media.BUCKET_ID} = ?")
putStringArray(android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(albumId))
}
},
null
)
} else {
videoCursor = this.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Video.Media._ID),
if (albumId == allAlbumId) null else "${MediaStore.Video.Media.BUCKET_ID} = $albumId",
null,
MediaStore.Video.Media.DATE_TAKEN + " DESC LIMIT 1"
)
}
videoCursor?.use { cursor ->
if (cursor.moveToNext()) {
val idColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID)
@ -627,12 +730,14 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
val widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH)
val heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT)
val mimeColumn = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)
val dateTakenColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN)
val dateModifiedColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED)
val id = cursor.getLong(idColumn)
val width = cursor.getLong(widthColumn)
val height = cursor.getLong(heightColumn)
val mimeType = cursor.getString(mimeColumn)
var dateTaken: Long? = null
if (cursor.getType(dateTakenColumn) == FIELD_TYPE_INTEGER) {
dateTaken = cursor.getLong(dateTakenColumn)
@ -647,6 +752,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
"mediumType" to imageType,
"width" to width,
"height" to height,
"mimeType" to mimeType,
"creationDate" to dateTaken,
"modifiedDate" to dateModified
)
@ -656,6 +762,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
val idColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID)
val widthColumn = cursor.getColumnIndex(MediaStore.Video.Media.WIDTH)
val heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT)
val mimeColumn = cursor.getColumnIndex(MediaStore.Video.Media.MIME_TYPE)
val durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION)
val dateTakenColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATE_TAKEN)
val dateModifiedColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATE_MODIFIED)
@ -663,6 +770,7 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
val id = cursor.getLong(idColumn)
val width = cursor.getLong(widthColumn)
val height = cursor.getLong(heightColumn)
val mimeType = cursor.getString(mimeColumn)
val duration = cursor.getLong(durationColumn)
var dateTaken: Long? = null
if (cursor.getType(dateTakenColumn) == FIELD_TYPE_INTEGER) {
@ -678,11 +786,27 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler {
"mediumType" to videoType,
"width" to width,
"height" to height,
"mimeType" to mimeType,
"duration" to duration,
"creationDate" to dateTaken,
"modifiedDate" to dateModified
)
}
private fun getCachePath(): File? {
return this.context?.run {
val cachePath = File(this.cacheDir, "photo_gallery")
if (!cachePath.exists()) {
cachePath.mkdirs()
}
return@run cachePath
}
}
private fun cleanCache() {
val cachePath = getCachePath()
cachePath?.deleteRecursively()
}
}
class BackgroundAsyncTask<T>(val handler: () -> T, val post: (result: T) -> Unit) : AsyncTask<Void, Void, T>() {

View File

@ -1,250 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.13"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.12"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.12"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.6"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.4"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.1+1"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
photo_gallery:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.3.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.15"
transparent_image:
dependency: "direct main"
description:
name: transparent_image
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
video_player:
dependency: "direct main"
description:
name: video_player
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.12"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
video_player_web:
dependency: transitive
description:
name: video_player_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3+2"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.1"
sdks:
dart: ">=2.7.0 <3.0.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0"

View File

@ -75,6 +75,10 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
result(filepath?.replacingOccurrences(of: "file://", with: ""))
})
}
else if(call.method == "cleanCache") {
cleanCache()
result(nil)
}
else {
result(FlutterMethodNotImplemented)
}
@ -161,7 +165,7 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
return PHAsset.fetchAssets(with: options).count
}
return PHAsset.fetchAssets(in: collection!, options: options).count
return PHAsset.fetchAssets(in: collection ?? PHAssetCollection.init(), options: options).count
}
private func listMedia(albumId: String, skip: NSNumber?, take: NSNumber?, mediumType: String) -> NSDictionary {
@ -175,7 +179,7 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
let fetchResult = albumId == "__ALL__"
? PHAsset.fetchAssets(with: fetchOptions)
: PHAsset.fetchAssets(in: collection!, options: fetchOptions)
: PHAsset.fetchAssets(in: collection ?? PHAssetCollection.init(), options: fetchOptions)
let start = skip?.intValue ?? 0
let total = fetchResult.count
let end = take == nil ? total : min(start + take!.intValue, total)
@ -330,20 +334,21 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
options: options,
resultHandler: { (data: Data?, uti: String?, orientation, info) in
DispatchQueue.main.async(execute: {
guard let originalData = data else {
guard let imageData = data else {
completion(nil, NSError(domain: "photo_gallery", code: 404, userInfo: nil))
return
}
guard let jpgData = self.convertToJpeg(originalData: originalData) else {
completion(nil, NSError(domain: "photo_gallery", code: 500, userInfo: nil))
guard let assetUTI = uti else {
completion(nil, NSError(domain: "photo_gallery", code: 404, userInfo: nil))
return
}
// Writing to file
let filepath = self.exportPathForAsset(asset: asset, ext: ".jpg")
try! jpgData.write(to: filepath, options: .atomic)
let fileExt = self.extractFileExtensionFromUTI(uti: assetUTI)
let filepath = self.exportPathForAsset(asset: asset, ext: fileExt)
try! imageData.write(to: filepath, options: .atomic)
completion(filepath.absoluteString, nil)
})
})
}
)
} else if(asset.mediaType == PHAssetMediaType.video
|| asset.mediaType == PHAssetMediaType.audio) {
let options = PHVideoRequestOptions()
@ -356,22 +361,26 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
do {
let avAsset = avAsset as? AVURLAsset
let data = try Data(contentsOf: avAsset!.url)
let filepath = self.exportPathForAsset(asset: asset, ext: ".mov")
let fileExt = self.extractFileExtensionFromAsset(asset: asset)
let filepath = self.exportPathForAsset(asset: asset, ext: fileExt)
try! data.write(to: filepath, options: .atomic)
completion(filepath.absoluteString, nil)
} catch {
completion(nil, NSError(domain: "photo_gallery", code: 500, userInfo: nil))
}
})
})
}
)
}
}
}
private func getMediumFromAsset(asset: PHAsset) -> [String: Any?] {
let mimeType = self.extractMimeTypeFromAsset(asset: asset)
return [
"id": asset.localIdentifier,
"mediumType": toDartMediumType(value: asset.mediaType),
"mimeType": mimeType,
"height": asset.pixelHeight,
"width": asset.pixelWidth,
"duration": NSInteger(asset.duration * 1000),
@ -380,36 +389,12 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
]
}
/// Converts to JPEG, and keep EXIF data.
private func convertToJpeg(originalData: Data) -> Data? {
guard let image: UIImage = UIImage(data: originalData) else { return nil }
let originalSrc = CGImageSourceCreateWithData(originalData as CFData, nil)!
let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse]
let originalMetadata = CGImageSourceCopyPropertiesAtIndex(originalSrc, 0, options as CFDictionary)
guard let jpeg = image.jpegData(compressionQuality: 1.0) else { return nil }
let src = CGImageSourceCreateWithData(jpeg as CFData, nil)!
let data = NSMutableData()
let uti = CGImageSourceGetType(src)!
let dest = CGImageDestinationCreateWithData(data as CFMutableData, uti, 1, nil)!
CGImageDestinationAddImageFromSource(dest, src, 0, originalMetadata)
if !CGImageDestinationFinalize(dest) { return nil }
return data as Data
}
private func exportPathForAsset(asset: PHAsset, ext: String) -> URL {
let mediumId = asset.localIdentifier
.replacingOccurrences(of: "/", with: "__")
.replacingOccurrences(of: "\\", with: "__")
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let tempFolder = paths[0].appendingPathComponent("photo_gallery")
try! FileManager.default.createDirectory(at: tempFolder, withIntermediateDirectories: true, attributes: nil)
return paths[0].appendingPathComponent(mediumId+ext)
let cachePath = self.cachePath()
return cachePath.appendingPathComponent(mediumId + ext)
}
private func toSwiftMediumType(value: String) -> PHAssetMediaType? {
@ -441,4 +426,55 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin {
let swiftType = toSwiftMediumType(value: mediumType)
return NSPredicate(format: "mediaType = %d", swiftType!.rawValue)
}
private func extractFileExtensionFromUTI(uti: String?) -> String {
guard let assetUTI = uti else {
return ""
}
guard let ext = UTTypeCopyPreferredTagWithClass(assetUTI as CFString, kUTTagClassFilenameExtension as CFString)?.takeRetainedValue() as String? else {
return ""
}
return "." + ext
}
private func extractMimeTypeFromUTI(uti: String?) -> String? {
guard let assetUTI = uti else {
return nil
}
guard let mimeType = UTTypeCopyPreferredTagWithClass(assetUTI as CFString, kUTTagClassMIMEType as CFString)?.takeRetainedValue() as String? else {
return nil
}
return mimeType
}
private func extractUTIFromAsset(asset: PHAsset) -> String? {
if #available(iOS 9, *) {
let resourceList = PHAssetResource.assetResources(for: asset)
if let resource = resourceList.first {
return resource.uniformTypeIdentifier
}
}
return asset.value(forKey: "uniformTypeIdentifier") as? String
}
private func extractFileExtensionFromAsset(asset: PHAsset) -> String {
let uti = self.extractUTIFromAsset(asset: asset)
return self.extractFileExtensionFromUTI(uti: uti)
}
private func extractMimeTypeFromAsset(asset: PHAsset) -> String? {
let uti = self.extractUTIFromAsset(asset: asset)
return self.extractMimeTypeFromUTI(uti: uti)
}
private func cachePath() -> URL {
let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let cacheFolder = paths[0].appendingPathComponent("photo_gallery")
try! FileManager.default.createDirectory(at: cacheFolder, withIntermediateDirectories: true, attributes: nil)
return cacheFolder
}
private func cleanCache() {
try? FileManager.default.removeItem(at: self.cachePath())
}
}

View File

@ -113,4 +113,8 @@ class PhotoGallery {
}) as String;
return File(path);
}
static Future<void> cleanCache() async {
_channel.invokeMethod('cleanCache', {});
}
}

View File

@ -17,6 +17,9 @@ class Medium {
/// The medium height.
final int height;
/// The medium mimeType.
final String mimeType;
/// The duration of video
final int duration;
@ -31,6 +34,7 @@ class Medium {
this.mediumType,
this.width,
this.height,
this.mimeType,
this.duration,
this.creationDate,
this.modifiedDate,
@ -42,6 +46,7 @@ class Medium {
mediumType = jsonToMediumType(json["mediumType"]),
width = json["width"],
height = json["height"],
mimeType = json["mimeType"],
duration = json['duration'] ?? 0,
creationDate = json['creationDate'] != null
? DateTime.fromMillisecondsSinceEpoch(json['creationDate'])
@ -56,6 +61,7 @@ class Medium {
mediumType: jsonToMediumType(map['mediumType']),
width: map['width'],
height: map['height'],
mimeType: map["mimeType"],
creationDate: map['creationDate'],
modifiedDate: map['modifiedDate'],
);
@ -66,6 +72,7 @@ class Medium {
"id": this.id,
"mediumType": mediumTypeToJson(this.mediumType),
"height": this.height,
"mimeType": this.mimeType,
"width": this.width,
"creationDate": this.creationDate,
"modifiedDate": this.modifiedDate,
@ -104,6 +111,7 @@ class Medium {
mediumType == other.mediumType &&
width == other.width &&
height == other.height &&
mimeType == other.mimeType &&
creationDate == other.creationDate &&
modifiedDate == other.modifiedDate;
@ -113,6 +121,7 @@ class Medium {
mediumType.hashCode ^
width.hashCode ^
height.hashCode ^
mimeType.hashCode ^
creationDate.hashCode ^
modifiedDate.hashCode;
@ -122,6 +131,7 @@ class Medium {
'mediumType: $mediumType, '
'width: $width, '
'height: $height, '
'mimeType: $mimeType, '
'creationDate: $creationDate, '
'modifiedDate: $modifiedDate}';
}

View File

@ -1,182 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.13"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.12"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.12"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.6"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.4"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.15"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.1"
sdks:
dart: ">=2.7.0 <3.0.0"
flutter: ">=1.10.0"

View File

@ -1,6 +1,6 @@
name: photo_gallery
description: A Flutter plugin that retrieves images and videos from mobile native gallery.
version: 0.3.0
version: 0.4.0
repository: https://github.com/Firelands128/photo_gallery
environment:

View File

@ -61,7 +61,10 @@ class Generator {
"mediumType": mediumTypeToJson(mediumType),
"width": 512,
"height": 512,
"mimeType": "image/jpeg",
"duration": 3600,
"creationDate": DateTime(2020, 8, 1).millisecondsSinceEpoch,
"modifiedDate": DateTime(2020, 9, 1).millisecondsSinceEpoch,
};
}